diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index aa35797d1f986..cc625a09fadd0 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -142,6 +142,7 @@ enabled: - x-pack/test/detection_engine_api_integration/security_and_spaces/group8/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group9/config.ts - x-pack/test/detection_engine_api_integration/security_and_spaces/group10/config.ts + - x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts - x-pack/test/encrypted_saved_objects_api_integration/config.ts - x-pack/test/endpoint_api_integration_no_ingest/config.ts - x-pack/test/examples/config.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 50bd3144b1ccb..4dc48e657542b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -47,8 +47,6 @@ /x-pack/test/functional/apps/lens @elastic/kibana-vis-editors /x-pack/test/api_integration/apis/lens/ @elastic/kibana-vis-editors /test/functional/apps/visualize/ @elastic/kibana-vis-editors -/src/plugins/unified_field_list/ @elastic/kibana-vis-editors -/test/api_integration/apis/unified_field_list/ @elastic/kibana-vis-editors # Application Services /examples/bfetch_explorer/ @elastic/kibana-app-services @@ -143,7 +141,6 @@ x-pack/examples/files_example @elastic/kibana-app-services /src/core/types/elasticsearch @elastic/apm-ui /packages/kbn-utility-types/src/dot.ts @dgieselaar /packages/kbn-utility-types/src/dot_test.ts @dgieselaar -/packages/kbn-adhoc-profiler @elastic/apm-ui #CC# /src/plugins/apm_oss/ @elastic/apm-ui #CC# /x-pack/plugins/observability/ @elastic/apm-ui @@ -478,26 +475,31 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/security_solution/common/detection_engine/schemas/alerts @elastic/security-detections-response-alerts /x-pack/plugins/security_solution/common/field_maps @elastic/security-detections-response-alerts +/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui @elastic/security-detections-response-alerts /x-pack/plugins/security_solution/public/detections/pages/alerts @elastic/security-detections-response-alerts /x-pack/plugins/security_solution/server/lib/detection_engine/migrations @elastic/security-detections-response-alerts -/x-pack/plugins/security_solution/server/lib/detection_engine/notifications @elastic/security-detections-response-alerts -/x-pack/plugins/security_solution/server/lib/detection_engine/schemas @elastic/security-detections-response-alerts +/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview @elastic/security-detections-response-alerts /x-pack/plugins/security_solution/server/lib/detection_engine/rule_types @elastic/security-detections-response-alerts /x-pack/plugins/security_solution/server/lib/detection_engine/signals @elastic/security-detections-response-alerts /x-pack/plugins/security_solution/server/lib/detection_engine/routes/index @elastic/security-detections-response-alerts /x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals @elastic/security-detections-response-alerts ## Security Solution sub teams - Detections and Response Rules +/x-pack/plugins/security_solution/common/detection_engine/fleet_integrations @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/common/detection_engine/rule_management @elastic/security-detections-response-rules /x-pack/plugins/security_solution/common/detection_engine/rule_monitoring @elastic/security-detections-response-rules -/x-pack/plugins/security_solution/common/detection_engine/schemas/common @elastic/security-detections-response-rules -/x-pack/plugins/security_solution/common/detection_engine/schemas/request @elastic/security-detections-response-rules -/x-pack/plugins/security_solution/common/detection_engine/schemas/response @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/common/detection_engine/rule_schema @elastic/security-detections-response-rules @elastic/security-detections-response-alerts /x-pack/plugins/security_solution/public/common/components/health_truncate_text @elastic/security-detections-response-rules /x-pack/plugins/security_solution/public/common/components/links_to_docs @elastic/security-detections-response-rules /x-pack/plugins/security_solution/public/common/components/ml_popover @elastic/security-detections-response-rules /x-pack/plugins/security_solution/public/common/components/popover_items @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/public/detection_engine/rule_management @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui @elastic/security-detections-response-rules /x-pack/plugins/security_solution/public/detection_engine/rule_monitoring @elastic/security-detections-response-rules /x-pack/plugins/security_solution/public/detections/components/callouts @elastic/security-detections-response-rules /x-pack/plugins/security_solution/public/detections/components/modals/ml_job_upgrade_modal @elastic/security-detections-response-rules @@ -508,17 +510,12 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules @elastic/security-detections-response-rules /x-pack/plugins/security_solution/public/rules @elastic/security-detections-response-rules -/x-pack/plugins/security_solution/server/lib/detection_engine/routes/fleet @elastic/security-detections-response-rules -/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules @elastic/security-detections-response-rules -/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rule_exceptions_route* @elastic/security-solution-platform -/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_exceptions_route* @elastic/security-solution-platform -/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route* @elastic/security-solution-platform -/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route* @elastic/security-detections-response-alerts -/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils @elastic/security-solution-platform -/x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management @elastic/security-detections-response-rules /x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring @elastic/security-detections-response-rules -/x-pack/plugins/security_solution/server/lib/detection_engine/rules @elastic/security-detections-response-rules -/x-pack/plugins/security_solution/server/lib/detection_engine/tags @elastic/security-detections-response-rules +/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema @elastic/security-detections-response-rules @elastic/security-detections-response-alerts + /x-pack/plugins/security_solution/server/utils @elastic/security-detections-response-rules ## Security Solution sub teams - Security Platform @@ -528,12 +525,17 @@ x-pack/examples/files_example @elastic/kibana-app-services /x-pack/plugins/security_solution/cypress/e2e/exceptions @elastic/security-solution-platform /x-pack/plugins/security_solution/cypress/e2e/value_lists @elastic/security-solution-platform +/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions @elastic/security-solution-platform + /x-pack/plugins/security_solution/public/detection_engine/rule_exceptions @elastic/security-solution-platform +/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui @elastic/security-solution-platform /x-pack/plugins/security_solution/public/common/components/exceptions @elastic/security-solution-platform /x-pack/plugins/security_solution/public/exceptions @elastic/security-solution-platform /x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists @elastic/security-solution-platform /x-pack/plugins/security_solution/public/common/components/sourcerer @elastic/security-solution-platform +/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy @elastic/security-solution-platform +/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions @elastic/security-solution-platform /x-pack/plugins/security_solution/server/lib/sourcerer @elastic/security-solution-platform ## Security Threat Intelligence - Under Security Platform @@ -596,7 +598,7 @@ x-pack/test/threat_intelligence_cypress @elastic/protections-experience # Security Intelligence And Analytics -/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules @elastic/security-intelligence-analytics +/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules @elastic/security-intelligence-analytics # Security Asset Management @@ -780,6 +782,9 @@ packages/core/integrations/core-integrations-browser-mocks @elastic/kibana-core packages/core/lifecycle/core-lifecycle-browser @elastic/kibana-core packages/core/lifecycle/core-lifecycle-browser-internal @elastic/kibana-core packages/core/lifecycle/core-lifecycle-browser-mocks @elastic/kibana-core +packages/core/lifecycle/core-lifecycle-server @elastic/kibana-core +packages/core/lifecycle/core-lifecycle-server-internal @elastic/kibana-core +packages/core/lifecycle/core-lifecycle-server-mocks @elastic/kibana-core packages/core/logging/core-logging-server @elastic/kibana-core packages/core/logging/core-logging-server-internal @elastic/kibana-core packages/core/logging/core-logging-server-mocks @elastic/kibana-core @@ -803,6 +808,9 @@ packages/core/plugins/core-plugins-base-server-internal @elastic/kibana-core packages/core/plugins/core-plugins-browser @elastic/kibana-core packages/core/plugins/core-plugins-browser-internal @elastic/kibana-core packages/core/plugins/core-plugins-browser-mocks @elastic/kibana-core +packages/core/plugins/core-plugins-server @elastic/kibana-core +packages/core/plugins/core-plugins-server-internal @elastic/kibana-core +packages/core/plugins/core-plugins-server-mocks @elastic/kibana-core packages/core/preboot/core-preboot-server @elastic/kibana-core packages/core/preboot/core-preboot-server-internal @elastic/kibana-core packages/core/preboot/core-preboot-server-mocks @elastic/kibana-core @@ -856,7 +864,6 @@ packages/home/sample_data_card @elastic/shared-ux packages/home/sample_data_tab @elastic/shared-ux packages/home/sample_data_types @elastic/shared-ux packages/kbn-ace @elastic/platform-deployment-management -packages/kbn-adhoc-profiler @elastic/apm-ui packages/kbn-alerts @elastic/security-solution packages/kbn-ambient-storybook-types @elastic/kibana-operations packages/kbn-ambient-ui-types @elastic/kibana-operations diff --git a/.gitignore b/.gitignore index 82a13e661a5bb..81b0d437f8126 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,12 @@ npm-debug.log* ## @cypress/snapshot from apm plugin /snapshots.js +# transpiled cypress config +x-pack/plugins/fleet/cypress.config.d.ts +x-pack/plugins/fleet/cypress.config.js +x-pack/plugins/osquery/cypress.config.d.ts +x-pack/plugins/osquery/cypress.config.js + # release notes script output report.csv report.asciidoc diff --git a/api_docs/actions.devdocs.json b/api_docs/actions.devdocs.json index 0f63f83fedf12..278caf26266e3 100644 --- a/api_docs/actions.devdocs.json +++ b/api_docs/actions.devdocs.json @@ -772,6 +772,102 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "actions", + "id": "def-server.UnsecuredActionsClient", + "type": "Class", + "tags": [], + "label": "UnsecuredActionsClient", + "description": [], + "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "actions", + "id": "def-server.UnsecuredActionsClient.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "actions", + "id": "def-server.UnsecuredActionsClient.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "UnsecuredActionsClientOpts" + ], + "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "actions", + "id": "def-server.UnsecuredActionsClient.bulkEnqueueExecution", + "type": "Function", + "tags": [], + "label": "bulkEnqueueExecution", + "description": [], + "signature": [ + "(requesterId: string, actionsToExecute: ", + "ExecuteOptions", + "[]) => Promise" + ], + "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "actions", + "id": "def-server.UnsecuredActionsClient.bulkEnqueueExecution.$1", + "type": "string", + "tags": [], + "label": "requesterId", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "actions", + "id": "def-server.UnsecuredActionsClient.bulkEnqueueExecution.$2", + "type": "Array", + "tags": [], + "label": "actionsToExecute", + "description": [], + "signature": [ + "ExecuteOptions", + "[]" + ], + "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false } ], "functions": [ @@ -1494,6 +1590,70 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "actions", + "id": "def-server.IUnsecuredActionsClient", + "type": "Interface", + "tags": [], + "label": "IUnsecuredActionsClient", + "description": [], + "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "actions", + "id": "def-server.IUnsecuredActionsClient.bulkEnqueueExecution", + "type": "Function", + "tags": [], + "label": "bulkEnqueueExecution", + "description": [], + "signature": [ + "(requesterId: string, actionsToExecute: ", + "ExecuteOptions", + "[]) => Promise" + ], + "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "actions", + "id": "def-server.IUnsecuredActionsClient.bulkEnqueueExecution.$1", + "type": "string", + "tags": [], + "label": "requesterId", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "actions", + "id": "def-server.IUnsecuredActionsClient.bulkEnqueueExecution.$2", + "type": "Array", + "tags": [], + "label": "actionsToExecute", + "description": [], + "signature": [ + "ExecuteOptions", + "[]" + ], + "path": "x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "actions", "id": "def-server.PreConfiguredAction", @@ -2240,6 +2400,29 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "actions", + "id": "def-server.PluginStartContract.getUnsecuredActionsClient", + "type": "Function", + "tags": [], + "label": "getUnsecuredActionsClient", + "description": [], + "signature": [ + "() => ", + { + "pluginId": "actions", + "scope": "server", + "docId": "kibActionsPluginApi", + "section": "def-server.IUnsecuredActionsClient", + "text": "IUnsecuredActionsClient" + } + ], + "path": "x-pack/plugins/actions/server/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "actions", "id": "def-server.PluginStartContract.renderActionParameterTemplates", diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 2ba7542f0c07b..97fa8a58713eb 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Response Ops](https://github.com/orgs/elastic/teams/response-ops) for q | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 214 | 0 | 209 | 23 | +| 225 | 0 | 220 | 24 | ## Client diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index d3c4a3465e0f2..f4a0edb8fdad4 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 7b8d4fb88c3b9..8e2dc536c4852 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index 2e701198049b0..56a1f7d4b752d 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -2848,7 +2848,11 @@ "GetGlobalExecutionKPIParams", ") => Promise<{ success: number; unknown: number; failure: number; warning: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; getRuleExecutionKPI: ({ id, dateStart, dateEnd, filter }: ", "GetRuleExecutionKPIParams", - ") => Promise<{ success: number; unknown: number; failure: number; warning: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; bulkEdit: Promise<{ success: number; unknown: number; failure: number; warning: number; activeAlerts: number; newAlerts: number; recoveredAlerts: number; erroredActions: number; triggeredActions: number; }>; bulkDeleteRules: (options: ", + "BulkDeleteOptions", + ") => Promise<{ errors: ", + "BulkDeleteError", + "[]; total: number; taskIdsFailedToBeDeleted: string[]; }>; bulkEdit: " ], "path": "x-pack/plugins/apm/server/plugin.ts", @@ -183,13 +171,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "<", "APMPluginStartDependencies", ", unknown>, plugins: ", @@ -220,13 +202,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "<", "APMPluginStartDependencies", ", unknown>" @@ -263,13 +239,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ") => void" ], "path": "x-pack/plugins/apm/server/plugin.ts", @@ -284,13 +254,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], "path": "x-pack/plugins/apm/server/plugin.ts", "deprecated": false, @@ -753,7 +717,7 @@ "label": "APIEndpoint", "description": [], "signature": [ - "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/nodes\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/sorted_and_filtered_services\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/service_groups/services_count\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_rate\" | \"GET /internal/apm/alerts/chart_preview/transaction_duration\" | \"GET /internal/apm/alerts/chart_preview/transaction_error_count\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"POST /internal/apm/correlations/field_stats/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\"" + "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/title\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics_by_transaction_name\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}/top_erroneous_transactions\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/nodes\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\" | \"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/service-map\" | \"GET /internal/apm/service-map/service/{serviceName}\" | \"GET /internal/apm/service-map/dependency\" | \"GET /internal/apm/services\" | \"POST /internal/apm/services/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/metadata/details\" | \"GET /internal/apm/services/{serviceName}/metadata/icons\" | \"GET /internal/apm/services/{serviceName}/agent\" | \"GET /internal/apm/services/{serviceName}/transaction_types\" | \"GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata\" | \"GET /api/apm/services/{serviceName}/annotation/search\" | \"POST /api/apm/services/{serviceName}/annotation\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/details/{serviceNodeName}\" | \"GET /internal/apm/services/{serviceName}/throughput\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics\" | \"GET /internal/apm/services/{serviceName}/service_overview_instances/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/dependencies\" | \"GET /internal/apm/services/{serviceName}/dependencies/breakdown\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/sorted_and_filtered_services\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"GET /internal/apm/service_groups/services_count\" | \"GET /internal/apm/suggestions\" | \"GET /internal/apm/traces/{traceId}\" | \"GET /internal/apm/traces\" | \"GET /internal/apm/traces/{traceId}/root_transaction\" | \"GET /internal/apm/transactions/{transactionId}\" | \"GET /internal/apm/traces/find\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/latency\" | \"GET /internal/apm/services/{serviceName}/transactions/traces/samples\" | \"GET /internal/apm/services/{serviceName}/transaction/charts/breakdown\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/error_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"GET /internal/apm/rule_types/transaction_error_rate/chart_preview\" | \"GET /internal/apm/rule_types/transaction_duration/chart_preview\" | \"GET /internal/apm/rule_types/error_count/chart_preview\" | \"GET /api/apm/settings/agent-configuration\" | \"GET /api/apm/settings/agent-configuration/view\" | \"DELETE /api/apm/settings/agent-configuration\" | \"PUT /api/apm/settings/agent-configuration\" | \"POST /api/apm/settings/agent-configuration/search\" | \"GET /api/apm/settings/agent-configuration/environments\" | \"GET /api/apm/settings/agent-configuration/agent_name\" | \"GET /internal/apm/settings/anomaly-detection/jobs\" | \"POST /internal/apm/settings/anomaly-detection/jobs\" | \"GET /internal/apm/settings/anomaly-detection/environments\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"GET /internal/apm/settings/apm-index-settings\" | \"GET /internal/apm/settings/apm-indices\" | \"POST /internal/apm/settings/apm-indices/save\" | \"GET /internal/apm/settings/custom_links/transaction\" | \"GET /internal/apm/settings/custom_links\" | \"POST /internal/apm/settings/custom_links\" | \"PUT /internal/apm/settings/custom_links/{id}\" | \"DELETE /internal/apm/settings/custom_links/{id}\" | \"GET /api/apm/sourcemaps\" | \"POST /api/apm/sourcemaps\" | \"DELETE /api/apm/sourcemaps/{id}\" | \"GET /internal/apm/fleet/has_apm_policies\" | \"GET /internal/apm/fleet/agents\" | \"POST /api/apm/fleet/apm_server_schema\" | \"GET /internal/apm/fleet/apm_server_schema/unsupported\" | \"GET /internal/apm/fleet/migration_check\" | \"POST /internal/apm/fleet/cloud_apm_package_policy\" | \"GET /internal/apm/fleet/java_agent_versions\" | \"GET /internal/apm/dependencies/top_dependencies\" | \"GET /internal/apm/dependencies/upstream_services\" | \"GET /internal/apm/dependencies/metadata\" | \"GET /internal/apm/dependencies/charts/latency\" | \"GET /internal/apm/dependencies/charts/throughput\" | \"GET /internal/apm/dependencies/charts/error_rate\" | \"GET /internal/apm/dependencies/operations\" | \"GET /internal/apm/dependencies/charts/distribution\" | \"GET /internal/apm/dependencies/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"POST /internal/apm/correlations/field_stats/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/storage_explorer\" | \"GET /internal/apm/services/{serviceName}/storage_details\" | \"GET /internal/apm/storage_chart\" | \"GET /internal/apm/storage_explorer/privileges\" | \"GET /internal/apm/storage_explorer_summary_stats\" | \"GET /internal/apm/storage_explorer/is_cross_cluster_search\" | \"GET /internal/apm/storage_explorer/get_services\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\" | \"GET /internal/apm/settings/labs\"" ], "path": "x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts", "deprecated": false, @@ -770,7 +734,7 @@ "signature": [ "\"apm\"" ], - "path": "x-pack/plugins/apm/common/alert_types.ts", + "path": "x-pack/plugins/apm/common/rules/apm_rule_types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1027,6 +991,76 @@ "SpanLinkDetails", "[]; }, ", "APMRouteCreateOptions", + ">; \"GET /internal/apm/storage_explorer/get_services\": ", + "ServerRoute", + "<\"GET /internal/apm/storage_explorer/get_services\", ", + "TypeC", + "<{ query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ indexLifecyclePhase: ", + "UnionC", + "<[", + "LiteralC", + "<", + "IndexLifecyclePhaseSelectOption", + ".All>, ", + "LiteralC", + "<", + "IndexLifecyclePhaseSelectOption", + ".Hot>, ", + "LiteralC", + "<", + "IndexLifecyclePhaseSelectOption", + ".Warm>, ", + "LiteralC", + "<", + "IndexLifecyclePhaseSelectOption", + ".Cold>, ", + "LiteralC", + "<", + "IndexLifecyclePhaseSelectOption", + ".Frozen>]>; }>, ", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + "NonEmptyStringBrand", + ">]>; }>, ", + "TypeC", + "<{ kuery: ", + "StringC", + "; }>]>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { services: { serviceName: string; }[]; }, ", + "APMRouteCreateOptions", + ">; \"GET /internal/apm/storage_explorer/is_cross_cluster_search\": ", + "ServerRoute", + "<\"GET /internal/apm/storage_explorer/is_cross_cluster_search\", undefined, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { isCrossClusterSearch: boolean; }, ", + "APMRouteCreateOptions", ">; \"GET /internal/apm/storage_explorer_summary_stats\": ", "ServerRoute", "<\"GET /internal/apm/storage_explorer_summary_stats\", ", @@ -1093,7 +1127,7 @@ "section": "def-server.APMRouteHandlerResources", "text": "APMRouteHandlerResources" }, - ", { tracesPerMinute: number; numberOfServices: number; estimatedSize: number; dailyDataGeneration: number; }, ", + ", { tracesPerMinute: number; numberOfServices: number; totalSize: number; diskSpaceUsedPct: number; estimatedIncrementalSize: number; dailyDataGeneration: number; }, ", "APMRouteCreateOptions", ">; \"GET /internal/apm/storage_explorer/privileges\": ", "ServerRoute", @@ -1253,7 +1287,7 @@ "section": "def-common.ProcessorEvent", "text": "ProcessorEvent" }, - "; docs: number; size: number; }[]; }, ", + "; docs: number; size: number; }[]; indicesStats: { indexName: string; numberOfDocs: number; primary?: string | number | undefined; replica?: string | number | undefined; size?: number | undefined; dataStream?: string | undefined; lifecyclePhase?: string | undefined; }[]; }, ", "APMRouteCreateOptions", ">; \"GET /internal/apm/storage_explorer\": ", "ServerRoute", @@ -2971,9 +3005,9 @@ "AgentConfiguration", "[]; }, ", "APMRouteCreateOptions", - ">; \"GET /internal/apm/alerts/chart_preview/transaction_duration\": ", + ">; \"GET /internal/apm/rule_types/transaction_duration/chart_preview\": ", "ServerRoute", - "<\"GET /internal/apm/alerts/chart_preview/transaction_duration\", ", + "<\"GET /internal/apm/rule_types/transaction_duration/chart_preview\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -3031,9 +3065,9 @@ }, ", { latencyChartPreview: { name: string; data: { x: number; y: number | null; }[]; }[]; }, ", "APMRouteCreateOptions", - ">; \"GET /internal/apm/alerts/chart_preview/transaction_error_count\": ", + ">; \"GET /internal/apm/rule_types/error_count/chart_preview\": ", "ServerRoute", - "<\"GET /internal/apm/alerts/chart_preview/transaction_error_count\", ", + "<\"GET /internal/apm/rule_types/error_count/chart_preview\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -3091,9 +3125,9 @@ }, ", { errorCountChartPreview: { x: number; y: number; }[]; }, ", "APMRouteCreateOptions", - ">; \"GET /internal/apm/alerts/chart_preview/transaction_error_rate\": ", + ">; \"GET /internal/apm/rule_types/transaction_error_rate/chart_preview\": ", "ServerRoute", - "<\"GET /internal/apm/alerts/chart_preview/transaction_error_rate\", ", + "<\"GET /internal/apm/rule_types/transaction_error_rate/chart_preview\", ", "TypeC", "<{ query: ", "IntersectionC", @@ -5003,6 +5037,210 @@ }, ", { serviceCount: number; transactionPerMinute: { value: undefined; timeseries: never[]; } | { value: number; timeseries: { x: number; y: number | null; }[]; }; }, ", "APMRouteCreateOptions", + ">; \"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\": ", + "ServerRoute", + "<\"GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + "NonEmptyStringBrand", + ">]>; }>, ", + "TypeC", + "<{ kuery: ", + "StringC", + "; }>, ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>, ", + "PartialC", + "<{ serverlessId: ", + "StringC", + "; }>]>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { activeInstances: ", + "ActiveInstanceOverview", + "[]; timeseries: ", + "Coordinate", + "[]; }, ", + "APMRouteCreateOptions", + ">; \"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\": ", + "ServerRoute", + "<\"GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + "NonEmptyStringBrand", + ">]>; }>, ", + "TypeC", + "<{ kuery: ", + "StringC", + "; }>, ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>]>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { serverlessFunctionsOverview: { serverlessId: string; serverlessFunctionName: string; serverlessDurationAvg: number | null; billedDurationAvg: number | null; coldStartCount: number | null; avgMemoryUsed: number | undefined; memorySize: number | null; }[]; }, ", + "APMRouteCreateOptions", + ">; \"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\": ", + "ServerRoute", + "<\"GET /internal/apm/services/{serviceName}/metrics/serverless/summary\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + "NonEmptyStringBrand", + ">]>; }>, ", + "TypeC", + "<{ kuery: ", + "StringC", + "; }>, ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>, ", + "PartialC", + "<{ serverlessId: ", + "StringC", + "; }>]>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { memoryUsageAvgRate: number | undefined; serverlessFunctionsTotal: number | undefined; serverlessDurationAvg: number | null | undefined; billedDurationAvg: number | null | undefined; }, ", + "APMRouteCreateOptions", + ">; \"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\": ", + "ServerRoute", + "<\"GET /internal/apm/services/{serviceName}/metrics/serverless/charts\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ serviceName: ", + "StringC", + "; }>; query: ", + "IntersectionC", + "<[", + "TypeC", + "<{ environment: ", + "UnionC", + "<[", + "LiteralC", + "<\"ENVIRONMENT_NOT_DEFINED\">, ", + "LiteralC", + "<\"ENVIRONMENT_ALL\">, ", + "BrandC", + "<", + "StringC", + ", ", + "NonEmptyStringBrand", + ">]>; }>, ", + "TypeC", + "<{ kuery: ", + "StringC", + "; }>, ", + "TypeC", + "<{ start: ", + "Type", + "; end: ", + "Type", + "; }>, ", + "PartialC", + "<{ serverlessId: ", + "StringC", + "; }>]>; }>, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { charts: [", + "FetchAndTransformMetrics", + ", ", + "FetchAndTransformMetrics", + ", { series: { overallValue: number; data: { x: number; y: number | null | undefined; }[]; title: string; key: string; type: ", + "ChartType", + "; color: string; }[]; title: string; key: string; yUnit: ", + "YUnit", + "; description?: string | undefined; }, ", + "FetchAndTransformMetrics", + ", ", + "FetchAndTransformMetrics", + "]; }, ", + "APMRouteCreateOptions", ">; \"GET /internal/apm/services/{serviceName}/metrics/nodes\": ", "ServerRoute", "<\"GET /internal/apm/services/{serviceName}/metrics/nodes\", ", @@ -5065,8 +5303,6 @@ "PartialC", "<{ serviceNodeName: ", "StringC", - "; serviceRuntimeName: ", - "StringC", "; }>, ", "TypeC", "<{ environment: ", diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index f20727d06136f..e22939b28442c 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; @@ -21,7 +21,7 @@ Contact [APM UI](https://github.com/orgs/elastic/teams/apm-ui) for questions reg | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 38 | 0 | 38 | 53 | +| 38 | 0 | 38 | 56 | ## Client diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index c2d744dd827a1..f16f3cf9d25eb 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: 2022-10-20 +date: 2022-10-27 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 d36bbdf86ecf8..25221b9f93372 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: 2022-10-20 +date: 2022-10-27 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 5437c10f4d717..f54c7efaa9edf 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: 2022-10-20 +date: 2022-10-27 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 1de3ab4a521c3..9645e8dcf1d0a 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: 2022-10-20 +date: 2022-10-27 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 a48c8fe6661d1..5631dbdeca13b 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: 2022-10-20 +date: 2022-10-27 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 7dc42f6387741..5b2a574909627 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_chat.mdx b/api_docs/cloud_chat.mdx index f965713b58874..e1909e7a093af 100644 --- a/api_docs/cloud_chat.mdx +++ b/api_docs/cloud_chat.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudChat title: "cloudChat" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudChat plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 4dc125f728eca..1c78ae2cbf9ef 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 9bd0886195c83..281cebcabf59e 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: 2022-10-20 +date: 2022-10-27 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 e1e057275a72c..7a9eaac825214 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.devdocs.json b/api_docs/controls.devdocs.json index c83780deefca3..09686e7655b5b 100644 --- a/api_docs/controls.devdocs.json +++ b/api_docs/controls.devdocs.json @@ -258,7 +258,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/controls/public/control_group/embeddable/control_group_container.tsx", "deprecated": false, @@ -939,7 +939,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>" + ", any>, unknown>" ], "path": "src/plugins/controls/public/control_group/embeddable/control_group_container_factory.ts", "deprecated": false, @@ -1337,7 +1337,7 @@ "section": "def-public.ControlOutput", "text": "ControlOutput" }, - ">" + ", any>" ], "path": "src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx", "deprecated": false, @@ -1632,7 +1632,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>,", + ", any>, unknown>,", { "pluginId": "controls", "scope": "public", @@ -2245,7 +2245,7 @@ "section": "def-public.ControlOutput", "text": "ControlOutput" }, - ">" + ", any>" ], "path": "src/plugins/controls/public/range_slider/embeddable/range_slider_embeddable.tsx", "deprecated": false, @@ -2540,7 +2540,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>,", + ", any>, unknown>,", { "pluginId": "controls", "scope": "public", @@ -3739,6 +3739,34 @@ "path": "src/plugins/controls/common/options_list/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "controls", + "id": "def-public.OptionsListEmbeddableInput.hideExclude", + "type": "CompoundType", + "tags": [], + "label": "hideExclude", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/controls/common/options_list/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "controls", + "id": "def-public.OptionsListEmbeddableInput.exclude", + "type": "CompoundType", + "tags": [], + "label": "exclude", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/controls/common/options_list/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -3885,7 +3913,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " & { isChained?: (() => boolean) | undefined; renderPrepend?: (() => React.ReactNode) | undefined; }" + " & { isChained?: (() => boolean) | undefined; renderPrepend?: (() => React.ReactNode) | undefined; }" ], "path": "src/plugins/controls/public/types.ts", "deprecated": false, @@ -4838,6 +4866,34 @@ "path": "src/plugins/controls/common/options_list/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "controls", + "id": "def-common.OptionsListEmbeddableInput.hideExclude", + "type": "CompoundType", + "tags": [], + "label": "hideExclude", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/controls/common/options_list/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "controls", + "id": "def-common.OptionsListEmbeddableInput.exclude", + "type": "CompoundType", + "tags": [], + "label": "exclude", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/controls/common/options_list/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 7eb5069f01084..d3472eb71af70 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-prese | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 229 | 0 | 220 | 7 | +| 233 | 0 | 224 | 7 | ## Client diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index 61651256d45da..34c162ad5a2a1 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -548,6 +548,10 @@ "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" @@ -588,6 +592,30 @@ "plugin": "@kbn/core-analytics-server-internal", "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, { "plugin": "@kbn/core-root-browser-internal", "path": "packages/core/root/core-root-browser-internal/src/core_system.ts" @@ -684,6 +712,14 @@ "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, { "plugin": "@kbn/core-analytics-server-internal", "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts" @@ -922,6 +958,10 @@ "plugin": "telemetry", "path": "src/plugins/telemetry/server/plugin.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" @@ -978,6 +1018,22 @@ "plugin": "@kbn/core-execution-context-browser-internal", "path": "packages/core/execution-context/core-execution-context-browser-internal/src/execution_context_service.ts" }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, { "plugin": "@kbn/core-status-server-internal", "path": "packages/core/status/core-status-server-internal/src/status_service.ts" @@ -1076,19 +1132,19 @@ }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-server-internal", @@ -4138,6 +4194,64 @@ ], "returnComment": [] }, + { + "parentPluginId": "core", + "id": "def-public.ChromeStart.getGlobalHelpExtensionMenuLinks$", + "type": "Function", + "tags": [], + "label": "getGlobalHelpExtensionMenuLinks$", + "description": [ + "\nGet the list of the registered global help extension menu links" + ], + "signature": [ + "() => ", + "Observable", + "<", + "ChromeGlobalHelpExtensionMenuLink", + "[]>" + ], + "path": "node_modules/@types/kbn__core-chrome-browser/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "core", + "id": "def-public.ChromeStart.registerGlobalHelpExtensionMenuLink", + "type": "Function", + "tags": [], + "label": "registerGlobalHelpExtensionMenuLink", + "description": [ + "\nAppend a global help extension menu link" + ], + "signature": [ + "(globalHelpExtensionMenuLink: ", + "ChromeGlobalHelpExtensionMenuLink", + ") => void" + ], + "path": "node_modules/@types/kbn__core-chrome-browser/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-public.ChromeStart.registerGlobalHelpExtensionMenuLink.$1", + "type": "Object", + "tags": [], + "label": "globalHelpExtensionMenuLink", + "description": [], + "signature": [ + "ChromeGlobalHelpExtensionMenuLink" + ], + "path": "node_modules/@types/kbn__core-chrome-browser/index.d.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "core", "id": "def-public.ChromeStart.getHelpExtension$", @@ -4145,7 +4259,7 @@ "tags": [], "label": "getHelpExtension$", "description": [ - "\nGet an observable of the current custom help conttent" + "\nGet an observable of the current custom help content" ], "signature": [ "() => ", @@ -10552,6 +10666,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "core", + "id": "def-public.SavedObject.created_at", + "type": "string", + "tags": [], + "label": "created_at", + "description": [ + "Timestamp of the time this document had been created." + ], + "signature": [ + "string | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-common/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "core", "id": "def-public.SavedObject.updated_at", @@ -11160,19 +11290,19 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_types.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_types.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.ts" }, { "plugin": "taskManager", @@ -13866,6 +13996,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "core", + "id": "def-public.SimpleSavedObject.createdAt", + "type": "string", + "tags": [], + "label": "createdAt", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-api-browser/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "core", "id": "def-public.SimpleSavedObject.namespaces", @@ -19319,6 +19463,10 @@ "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" @@ -19359,6 +19507,30 @@ "plugin": "@kbn/core-analytics-server-internal", "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, { "plugin": "@kbn/core-root-browser-internal", "path": "packages/core/root/core-root-browser-internal/src/core_system.ts" @@ -19455,6 +19627,14 @@ "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, { "plugin": "@kbn/core-analytics-server-internal", "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts" @@ -19693,6 +19873,10 @@ "plugin": "telemetry", "path": "src/plugins/telemetry/server/plugin.ts" }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, { "plugin": "@kbn/core-analytics-browser-internal", "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" @@ -19749,6 +19933,22 @@ "plugin": "@kbn/core-execution-context-browser-internal", "path": "packages/core/execution-context/core-execution-context-browser-internal/src/execution_context_service.ts" }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, { "plugin": "@kbn/core-status-server-internal", "path": "packages/core/status/core-status-server-internal/src/status_service.ts" @@ -19847,19 +20047,19 @@ }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-browser-internal", - "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" }, { "plugin": "@kbn/core-analytics-server-internal", @@ -20077,20 +20277,23 @@ "\nA plugin with asynchronous lifecycle methods.\n" ], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.AsyncPlugin", - "text": "AsyncPlugin" - }, + "AsyncPlugin", "" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": true, "removeBy": "8.8.0", "trackAdoption": false, - "references": [], + "references": [ + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin.ts" + } + ], "children": [ { "parentPluginId": "core", @@ -20101,16 +20304,10 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", ", plugins: TPluginsSetup) => TSetup | Promise" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -20122,16 +20319,10 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -20146,7 +20337,7 @@ "signature": [ "TPluginsSetup" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -20163,16 +20354,10 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ", plugins: TPluginsStart) => TStart | Promise" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -20184,15 +20369,9 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -20207,7 +20386,7 @@ "signature": [ "TPluginsStart" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -20225,7 +20404,7 @@ "signature": [ "(() => void) | undefined" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -21514,7 +21693,10 @@ "description": [ "\nContext passed to the `setup` method of `preboot` plugins." ], - "path": "src/core/server/index.ts", + "signature": [ + "CorePreboot" + ], + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -21546,7 +21728,7 @@ "ContextProviderOpts", ") => void; removeContextProvider: (contextProviderName: string) => void; }" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21562,7 +21744,7 @@ "signature": [ "ElasticsearchServicePreboot" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21581,7 +21763,7 @@ "RequestHandlerContext", ">" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21597,7 +21779,7 @@ "signature": [ "PrebootServicePreboot" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false } @@ -21722,16 +21904,10 @@ "\nContext passed to the `setup` method of `standard` plugins.\n" ], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -21763,7 +21939,7 @@ "ContextProviderOpts", ") => void; removeContextProvider: (contextProviderName: string) => void; }" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21779,7 +21955,7 @@ "signature": [ "CapabilitiesSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21795,7 +21971,7 @@ "signature": [ "DocLinksServiceSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21811,7 +21987,7 @@ "signature": [ "ElasticsearchServiceSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21827,7 +22003,7 @@ "signature": [ "ExecutionContextSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21848,7 +22024,7 @@ "HttpResources", "; }" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21864,7 +22040,7 @@ "signature": [ "I18nServiceSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21880,7 +22056,7 @@ "signature": [ "LoggingServiceSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21896,7 +22072,7 @@ "signature": [ "MetricsServiceSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21912,7 +22088,7 @@ "signature": [ "SavedObjectsServiceSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21928,7 +22104,7 @@ "signature": [ "StatusServiceSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21944,7 +22120,7 @@ "signature": [ "UiSettingsServiceSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21960,7 +22136,7 @@ "signature": [ "DeprecationsServiceSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -21975,16 +22151,10 @@ ], "signature": [ "() => Promise<[", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ", TPluginsStart, TStart]>" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false, "returnComment": [], @@ -22002,7 +22172,10 @@ "description": [ "\nContext passed to the plugins `start` method.\n" ], - "path": "src/core/server/index.ts", + "signature": [ + "CoreStart" + ], + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -22024,7 +22197,7 @@ "TelemetryCounter", ">; }" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -22040,7 +22213,7 @@ "signature": [ "CapabilitiesStart" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -22056,7 +22229,7 @@ "signature": [ "DocLinksServiceSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -22072,7 +22245,7 @@ "signature": [ "ElasticsearchServiceStart" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -22088,7 +22261,7 @@ "signature": [ "ExecutionContextSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -22104,7 +22277,7 @@ "signature": [ "HttpServiceStart" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -22120,7 +22293,7 @@ "signature": [ "MetricsServiceSetup" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -22136,7 +22309,7 @@ "signature": [ "SavedObjectsServiceStart" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -22152,7 +22325,7 @@ "signature": [ "UiSettingsServiceStart" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false } @@ -25193,6 +25366,14 @@ "plugin": "@kbn/core-elasticsearch-server-internal", "path": "packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts" }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, { "plugin": "@kbn/core-elasticsearch-server-internal", "path": "packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts" @@ -37562,16 +37743,10 @@ "\nThe interface that should be returned by a `PluginInitializer` for a `standard` plugin.\n" ], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, + "Plugin", "" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -37584,16 +37759,10 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", ", plugins: TPluginsSetup) => TSetup" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -37605,16 +37774,10 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -37629,7 +37792,7 @@ "signature": [ "TPluginsSetup" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -37646,16 +37809,10 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ", plugins: TPluginsStart) => TStart" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -37667,15 +37824,9 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -37690,7 +37841,7 @@ "signature": [ "TPluginsStart" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -37708,7 +37859,7 @@ "signature": [ "(() => void) | undefined" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -37727,16 +37878,10 @@ "\nDescribes a plugin configuration properties.\n" ], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginConfigDescriptor", - "text": "PluginConfigDescriptor" - }, + "PluginConfigDescriptor", "" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -37753,7 +37898,7 @@ "ConfigDeprecationProvider", " | undefined" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -37767,16 +37912,10 @@ "\nList of configuration properties that will be available on the client-side plugin." ], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.ExposedToBrowserDescriptor", - "text": "ExposedToBrowserDescriptor" - }, + "ExposedToBrowserDescriptor", " | undefined" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -37793,7 +37932,7 @@ "Type", "" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -37807,16 +37946,10 @@ "\nExpose non-default configs to usage collection to be sent via telemetry.\nset a config to `true` to report the actual changed config value.\nset a config to `false` to report the changed config value as [redacted].\n\nAll changed configs except booleans and numbers will be reported\nas [redacted] unless otherwise specified.\n\n{@link MakeUsageFromSchema}" ], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.MakeUsageFromSchema", - "text": "MakeUsageFromSchema" - }, + "MakeUsageFromSchema", " | undefined" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false } @@ -37833,16 +37966,10 @@ "\nContext that's available to plugins during initialization stage.\n" ], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, + "PluginInitializerContext", "" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -37856,7 +37983,7 @@ "signature": [ "symbol" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -37874,7 +38001,7 @@ "PackageInfo", ">; instanceUuid: string; configs: readonly string[]; }" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -37890,7 +38017,7 @@ "signature": [ "NodeInfo" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -37906,7 +38033,7 @@ "signature": [ "LoggerFactory" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -37938,7 +38065,7 @@ "Observable", "; get: () => T; }" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false } @@ -37954,7 +38081,10 @@ "description": [ "\nDescribes the set of required and optional properties plugin can define in its\nmandatory JSON manifest file.\n" ], - "path": "src/core/server/plugins/types.ts", + "signature": [ + "PluginManifest" + ], + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -37967,7 +38097,7 @@ "description": [ "\nIdentifier of the plugin. Must be a string in camelCase. Part of a plugin public contract.\nOther plugins leverage it to access plugin API, navigate to the plugin, etc." ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -37980,7 +38110,7 @@ "description": [ "\nVersion of the plugin." ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -37993,7 +38123,7 @@ "description": [ "\nThe version of Kibana the plugin is compatible with, defaults to \"version\"." ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -38009,7 +38139,7 @@ "signature": [ "PluginType" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -38025,7 +38155,7 @@ "signature": [ "string | string[]" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -38041,7 +38171,7 @@ "signature": [ "readonly string[]" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -38057,7 +38187,7 @@ "signature": [ "readonly string[]" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -38073,7 +38203,7 @@ "signature": [ "readonly string[]" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -38086,7 +38216,7 @@ "description": [ "\nSpecifies whether plugin includes some client/browser specific functionality\nthat should be included into client bundle via `public/ui_plugin.js` file." ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -38099,7 +38229,7 @@ "description": [ "\nSpecifies whether plugin includes some server-side specific functionality." ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -38117,10 +38247,35 @@ "signature": [ "string[] | undefined" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": true, "trackAdoption": false, - "references": [] + "references": [ + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts" + } + ] }, { "parentPluginId": "core", @@ -38134,7 +38289,7 @@ "signature": [ "readonly string[] | undefined" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -38148,7 +38303,7 @@ "signature": [ "{ readonly name: string; readonly githubTeam?: string | undefined; }" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -38164,7 +38319,7 @@ "signature": [ "string | undefined" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false }, @@ -38180,7 +38335,7 @@ "signature": [ "boolean | undefined" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false } @@ -39492,16 +39647,10 @@ "\nThe interface that should be returned by a `PluginInitializer` for a `preboot` plugin.\n" ], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PrebootPlugin", - "text": "PrebootPlugin" - }, + "PrebootPlugin", "" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -39514,16 +39663,10 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CorePreboot", - "text": "CorePreboot" - }, + "CorePreboot", ", plugins: TPluginsSetup) => TSetup" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -39535,15 +39678,9 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CorePreboot", - "text": "CorePreboot" - } + "CorePreboot" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -39558,7 +39695,7 @@ "signature": [ "TPluginsSetup" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -39576,7 +39713,7 @@ "signature": [ "(() => void) | undefined" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -40262,6 +40399,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "core", + "id": "def-server.SavedObject.created_at", + "type": "string", + "tags": [], + "label": "created_at", + "description": [ + "Timestamp of the time this document had been created." + ], + "signature": [ + "string | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-common/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "core", "id": "def-server.SavedObject.updated_at", @@ -40870,19 +41023,19 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_types.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_types.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.ts" }, { "plugin": "taskManager", @@ -46341,6 +46494,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRawDocSource.created_at", + "type": "string", + "tags": [], + "label": "created_at", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-server/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "core", "id": "def-server.SavedObjectsRawDocSource.references", @@ -51758,17 +51925,15 @@ "\nType defining the list of configuration properties that will be exposed on the client-side\nObject properties can either be fully exposed\n" ], "signature": [ - "{ [Key in keyof T]?: (T[Key] extends Maybe ? boolean : T[Key] extends Maybe ? boolean | ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.ExposedToBrowserDescriptor", - "text": "ExposedToBrowserDescriptor" - }, + "{ [Key in keyof T]?: (T[Key] extends ", + "Maybe", + " ? boolean : T[Key] extends ", + "Maybe", + " ? boolean | ", + "ExposedToBrowserDescriptor", " : boolean) | undefined; }" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -52415,17 +52580,17 @@ "\nList of configuration values that will be exposed to usage collection.\nIf parent node or actual config path is set to `true` then the actual value\nof these configs will be reoprted.\nIf parent node or actual config path is set to `false` then the config\nwill be reported as [redacted].\n" ], "signature": [ - "{ [Key in keyof T]?: (T[Key] extends Maybe ? false : T[Key] extends Maybe ? boolean : T[Key] extends Maybe ? boolean | ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.MakeUsageFromSchema", - "text": "MakeUsageFromSchema" - }, + "{ [Key in keyof T]?: (T[Key] extends ", + "Maybe", + " ? false : T[Key] extends ", + "Maybe", + " ? boolean : T[Key] extends ", + "Maybe", + " ? boolean | ", + "MakeUsageFromSchema", " : boolean) | undefined; }" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -52787,7 +52952,7 @@ "Type", "" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -52803,40 +52968,16 @@ ], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, + "PluginInitializerContext", ") => ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, + "Plugin", " | ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PrebootPlugin", - "text": "PrebootPlugin" - }, + "PrebootPlugin", " | ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.AsyncPlugin", - "text": "AsyncPlugin" - }, + "AsyncPlugin", "" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "returnComment": [], @@ -52849,16 +52990,10 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, + "PluginInitializerContext", "" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false } @@ -54083,7 +54218,7 @@ "ByteSizeValue", ") => boolean; getValueInBytes: () => number; toString: (returnUnit?: ByteSizeValueUnit | undefined) => string; }>; }>; }" ], - "path": "src/core/server/plugins/types.ts", + "path": "node_modules/@types/kbn__core-plugins-server/index.d.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -54099,16 +54234,10 @@ ], "signature": [ "() => Promise<[", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ", TPluginsStart, TStart]>" ], - "path": "src/core/server/index.ts", + "path": "node_modules/@types/kbn__core-lifecycle-server/index.d.ts", "deprecated": false, "trackAdoption": false, "returnComment": [], diff --git a/api_docs/core.mdx b/api_docs/core.mdx index cef8140fc8f77..b8b5be21f4bde 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2693 | 0 | 23 | 0 | +| 2700 | 0 | 0 | 0 | ## Client diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index c4f6a7138fb07..217bdbe22a03a 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: 2022-10-20 +date: 2022-10-27 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 ec1e651a84cce..5ca3a0a085c1d 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: 2022-10-20 +date: 2022-10-27 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 a39f0a5630e2b..4b7e08de75b38 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index 56d5bed9224e6..adb716ba2c9e8 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -1400,6 +1400,36 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-public.AggConfigs.samplerConfig", + "type": "Object", + "tags": [], + "label": "samplerConfig", + "description": [], + "signature": [ + "{ probability: number; seed: number | undefined; }" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-public.AggConfigs.isSamplingEnabled", + "type": "Function", + "tags": [], + "label": "isSamplingEnabled", + "description": [], + "signature": [ + "() => boolean" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-public.AggConfigs.setTimeFields", @@ -2588,7 +2618,7 @@ "description": [], "signature": [ "PluginInitializerContext", - "; }>; asyncSearch: Readonly<{} & { waitForCompletion: moment.Duration; keepAlive: moment.Duration; batchedReduceSize: number; }>; sessions: Readonly<{} & { enabled: boolean; notTouchedTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>>" + "; }>; asyncSearch: Readonly<{ pollInterval?: number | undefined; } & { waitForCompletion: moment.Duration; keepAlive: moment.Duration; batchedReduceSize: number; }>; sessions: Readonly<{} & { enabled: boolean; notTouchedTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>>" ], "path": "src/plugins/data/public/plugin.ts", "deprecated": false, @@ -3509,7 +3539,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts" } ], "children": [], @@ -3823,19 +3853,19 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts" }, { "plugin": "inputControlVis", @@ -8035,6 +8065,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-public.SavedObject.created_at", + "type": "string", + "tags": [], + "label": "created_at", + "description": [ + "Timestamp of the time this document had been created." + ], + "signature": [ + "string | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-common/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "data", "id": "def-public.SavedObject.updated_at", @@ -11485,14 +11531,6 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "unifiedSearch", - "path": "src/plugins/unified_search/public/query_string_input/query_string_input.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/kibana_services.ts" - }, { "plugin": "infra", "path": "x-pack/plugins/infra/public/hooks/use_kibana_index_patterns.ts" @@ -11714,13 +11752,7 @@ "text": "DataServerPlugin" }, " implements ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, + "Plugin", "<", { "pluginId": "data", @@ -11769,14 +11801,8 @@ "label": "initializerContext", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, - "; }>; asyncSearch: Readonly<{} & { waitForCompletion: moment.Duration; keepAlive: moment.Duration; batchedReduceSize: number; }>; sessions: Readonly<{} & { enabled: boolean; notTouchedTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>>" + "PluginInitializerContext", + "; }>; asyncSearch: Readonly<{ pollInterval?: number | undefined; } & { waitForCompletion: moment.Duration; keepAlive: moment.Duration; batchedReduceSize: number; }>; sessions: Readonly<{} & { enabled: boolean; notTouchedTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>>" ], "path": "src/plugins/data/server/plugin.ts", "deprecated": false, @@ -11795,13 +11821,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "<", "DataPluginStartDependencies", ", ", @@ -11840,13 +11860,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "<", "DataPluginStartDependencies", ", ", @@ -11891,13 +11905,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ", { fieldFormats, dataViews, taskManager }: ", "DataPluginStartDependencies", ") => { datatableUtilities: ", @@ -11950,13 +11958,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], "path": "src/plugins/data/server/plugin.ts", "deprecated": false, @@ -12199,18 +12201,6 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/common/utils/field_existing_utils.ts" }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/hooks/use_data.ts" - }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector_service.ts" - }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx" - }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" @@ -12255,50 +12245,6 @@ "plugin": "infra", "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -12531,6 +12477,14 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, { "plugin": "reporting", "path": "x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts" @@ -12557,7 +12511,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx" + "path": "x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx" }, { "plugin": "securitySolution", @@ -12731,6 +12685,38 @@ "plugin": "ml", "path": "x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts" @@ -19683,18 +19669,6 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/common/utils/field_existing_utils.ts" }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/hooks/use_data.ts" - }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector_service.ts" - }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx" - }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" @@ -19739,50 +19713,6 @@ "plugin": "infra", "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -20015,6 +19945,14 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, { "plugin": "reporting", "path": "x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts" @@ -20041,7 +19979,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx" + "path": "x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx" }, { "plugin": "securitySolution", @@ -20215,6 +20153,38 @@ "plugin": "ml", "path": "x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts" @@ -25662,6 +25632,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-common.SavedObject.created_at", + "type": "string", + "tags": [], + "label": "created_at", + "description": [ + "Timestamp of the time this document had been created." + ], + "signature": [ + "string | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-common/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "data", "id": "def-common.SavedObject.updated_at", diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 53b9fae5b68de..6a152f1c66866 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3242 | 33 | 2516 | 24 | +| 3251 | 33 | 2523 | 24 | ## Client diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index f8134455e6755..4c8e53d0ef0fe 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3242 | 33 | 2516 | 24 | +| 3251 | 33 | 2523 | 24 | ## Client diff --git a/api_docs/data_search.devdocs.json b/api_docs/data_search.devdocs.json index 8747220113c14..f14a2145fda16 100644 --- a/api_docs/data_search.devdocs.json +++ b/api_docs/data_search.devdocs.json @@ -1485,7 +1485,7 @@ "label": "config", "description": [], "signature": [ - "Readonly<{} & { search: Readonly<{} & { aggs: Readonly<{} & { shardDelay: Readonly<{} & { enabled: boolean; }>; }>; asyncSearch: Readonly<{} & { waitForCompletion: moment.Duration; keepAlive: moment.Duration; batchedReduceSize: number; }>; sessions: Readonly<{} & { enabled: boolean; notTouchedTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>" + "Readonly<{} & { search: Readonly<{} & { aggs: Readonly<{} & { shardDelay: Readonly<{} & { enabled: boolean; }>; }>; asyncSearch: Readonly<{ pollInterval?: number | undefined; } & { waitForCompletion: moment.Duration; keepAlive: moment.Duration; batchedReduceSize: number; }>; sessions: Readonly<{} & { enabled: boolean; notTouchedTimeout: moment.Duration; maxUpdateRetries: number; defaultExpiration: moment.Duration; management: Readonly<{} & { refreshInterval: moment.Duration; maxSessions: number; refreshTimeout: moment.Duration; expiresSoonWarning: moment.Duration; }>; }>; }>; }>" ], "path": "src/plugins/data/server/search/session/session_service.ts", "deprecated": false, @@ -1519,13 +1519,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", ", deps: SetupDependencies) => void" ], "path": "src/plugins/data/server/search/session/session_service.ts", @@ -1540,13 +1534,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "" ], "path": "src/plugins/data/server/search/session/session_service.ts", @@ -1581,13 +1569,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ", deps: StartDependencies) => void" ], "path": "src/plugins/data/server/search/session/session_service.ts", @@ -1602,13 +1584,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], "path": "src/plugins/data/server/search/session/session_service.ts", "deprecated": false, @@ -2516,13 +2492,7 @@ "description": [], "signature": [ "({ savedObjects, elasticsearch }: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ") => (request: ", "KibanaRequest", ") => { getId: (args_0: ", @@ -2655,13 +2625,7 @@ "label": "{ savedObjects, elasticsearch }", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], "path": "src/plugins/data/server/search/session/session_service.ts", "deprecated": false, @@ -3160,13 +3124,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ") => (request: ", "KibanaRequest", ") => ", @@ -3184,13 +3142,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], "path": "src/plugins/data/server/search/session/types.ts", "deprecated": false, @@ -5052,6 +5004,36 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "data", + "id": "def-common.AggConfigs.samplerConfig", + "type": "Object", + "tags": [], + "label": "samplerConfig", + "description": [], + "signature": [ + "{ probability: number; seed: number | undefined; }" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggConfigs.isSamplingEnabled", + "type": "Function", + "tags": [], + "label": "isSamplingEnabled", + "description": [], + "signature": [ + "() => boolean" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "data", "id": "def-common.AggConfigs.setTimeFields", @@ -9881,7 +9863,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts" } ], "children": [], @@ -10195,19 +10177,19 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts" + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts" }, { "plugin": "inputControlVis", @@ -16775,6 +16757,34 @@ "path": "src/plugins/data/common/search/aggs/agg_configs.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggConfigsOptions.probability", + "type": "number", + "tags": [], + "label": "probability", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.AggConfigsOptions.samplerSeed", + "type": "number", + "tags": [], + "label": "samplerSeed", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "src/plugins/data/common/search/aggs/agg_configs.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -28804,6 +28814,20 @@ "path": "src/plugins/data/common/search/session/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "data", + "id": "def-common.SearchSessionStatusResponse.errors", + "type": "Array", + "tags": [], + "label": "errors", + "description": [], + "signature": [ + "string[] | undefined" + ], + "path": "src/plugins/data/common/search/session/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index de74ebd14d8fd..bfd955dd39f9f 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3242 | 33 | 2516 | 24 | +| 3251 | 33 | 2523 | 24 | ## Client diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index e7526b2d366cc..9a4dd2a78efcc 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: 2022-10-20 +date: 2022-10-27 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 30aaae06c143e..22b10e0018364 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: 2022-10-20 +date: 2022-10-27 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 d889d65cf65ea..cbb9a1b524b4e 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.devdocs.json b/api_docs/data_views.devdocs.json index cedcb41cb3040..5566ad1bdd962 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -209,18 +209,6 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/common/utils/field_existing_utils.ts" }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/hooks/use_data.ts" - }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector_service.ts" - }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx" - }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" @@ -265,50 +253,6 @@ "plugin": "infra", "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -541,6 +485,14 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, { "plugin": "reporting", "path": "x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts" @@ -567,7 +519,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx" + "path": "x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx" }, { "plugin": "securitySolution", @@ -741,6 +693,38 @@ "plugin": "ml", "path": "x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts" @@ -8136,18 +8120,6 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/common/utils/field_existing_utils.ts" }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/hooks/use_data.ts" - }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector_service.ts" - }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx" - }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" @@ -8192,50 +8164,6 @@ "plugin": "infra", "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -8468,6 +8396,14 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, { "plugin": "reporting", "path": "x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts" @@ -8494,7 +8430,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx" + "path": "x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx" }, { "plugin": "securitySolution", @@ -8668,6 +8604,38 @@ "plugin": "ml", "path": "x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts" @@ -10503,13 +10471,7 @@ "text": "DataViewsServerPlugin" }, " implements ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, + "Plugin", "<", { "pluginId": "dataViews", @@ -10570,13 +10532,7 @@ "label": "initializerContext", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, + "PluginInitializerContext", "" ], "path": "src/plugins/data_views/server/plugin.ts", @@ -10596,13 +10552,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "<", { "pluginId": "dataViews", @@ -10641,13 +10591,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "<", { "pluginId": "dataViews", @@ -10704,13 +10648,7 @@ "description": [], "signature": [ "({ uiSettings, capabilities }: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ", { fieldFormats }: ", { "pluginId": "dataViews", @@ -10747,13 +10685,7 @@ "label": "{ uiSettings, capabilities }", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], "path": "src/plugins/data_views/server/plugin.ts", "deprecated": false, @@ -15210,18 +15142,6 @@ "plugin": "unifiedFieldList", "path": "src/plugins/unified_field_list/common/utils/field_existing_utils.ts" }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/hooks/use_data.ts" - }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector_service.ts" - }, - { - "plugin": "aiops", - "path": "x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx" - }, { "plugin": "aiops", "path": "x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx" @@ -15266,50 +15186,6 @@ "plugin": "infra", "path": "x-pack/plugins/infra/common/log_views/resolved_log_view.ts" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts" - }, { "plugin": "dataVisualizer", "path": "x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector_service.ts" @@ -15542,6 +15418,14 @@ "plugin": "apm", "path": "x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts" }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, + { + "plugin": "canvas", + "path": "x-pack/plugins/canvas/public/services/kibana/data_views.ts" + }, { "plugin": "reporting", "path": "x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts" @@ -15568,7 +15452,7 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx" + "path": "x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx" }, { "plugin": "securitySolution", @@ -15742,6 +15626,38 @@ "plugin": "ml", "path": "x-pack/plugins/ml/public/application/services/new_job_capabilities/remove_nested_field_children.test.ts" }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts" + }, + { + "plugin": "observability", + "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/common.test.ts" @@ -23213,6 +23129,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "dataViews", + "id": "def-common.SavedObject.created_at", + "type": "string", + "tags": [], + "label": "created_at", + "description": [ + "Timestamp of the time this document had been created." + ], + "signature": [ + "string | undefined" + ], + "path": "node_modules/@types/kbn__core-saved-objects-common/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "dataViews", "id": "def-common.SavedObject.updated_at", diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 25afb00311744..b4766e2d9885c 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1020 | 0 | 229 | 2 | +| 1021 | 0 | 229 | 2 | ## Client diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index de8f8c678f99b..8a268d23ca716 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index aa80017cd3fe9..019e1115caa97 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -27,21 +27,19 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | | | savedObjects, embeddable, fleet, visualizations, dashboard, infra, canvas, graph, actions, alerting, enterpriseSearch, securitySolution, taskManager, savedSearch, ml, @kbn/core-saved-objects-server-internal | - | | | discover, maps, monitoring | - | -| | securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, maps, dataVisualizer, ml, fleet, visTypeTimeseries, apm, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | -| | securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, maps, dataVisualizer, ml, fleet, visTypeTimeseries, apm, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | -| | securitySolution, timelines, lists, threatIntelligence, data, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, maps, dataVisualizer, ml, fleet, visTypeTimeseries, apm, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover | - | +| | securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, dataVisualizer, ml, fleet, visTypeTimeseries, apm, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | +| | securitySolution, timelines, lists, threatIntelligence, dataViews, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, dataVisualizer, ml, fleet, visTypeTimeseries, apm, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover, data | - | +| | securitySolution, timelines, lists, threatIntelligence, data, dataViewEditor, unifiedSearch, triggersActionsUi, savedObjectsManagement, unifiedFieldList, aiops, presentationUtil, controls, lens, observability, infra, dataVisualizer, ml, fleet, visTypeTimeseries, apm, canvas, reporting, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega, discover | - | | | data, discover, embeddable | - | | | advancedSettings, discover | - | | | advancedSettings, discover | - | -| | unifiedSearch, maps, infra, graph, securitySolution, stackAlerts, inputControlVis, savedObjects | - | +| | infra, graph, securitySolution, stackAlerts, inputControlVis, savedObjects | - | | | securitySolution | - | | | encryptedSavedObjects, actions, data, ml, logstash, securitySolution, cloudChat | - | | | dashboard, dataVisualizer, stackAlerts, expressionPartitionVis | - | | | dataViews, maps | - | | | dataViews, maps | - | | | maps | - | -| | unifiedSearch | - | -| | unifiedSearch | - | | | visTypeTimeseries, graph, dataViewManagement, dataViews | - | | | visTypeTimeseries, graph, dataViewManagement, dataViews | - | | | visTypeTimeseries, graph, dataViewManagement | - | @@ -51,13 +49,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | dataViews, dataViewManagement | - | | | dataViewManagement, dataViews | - | | | dataViews, dataViewManagement | - | +| | unifiedSearch | - | +| | unifiedSearch | - | | | home, data, esUiShared, spaces, savedObjectsManagement, fleet, observability, ml, apm, indexLifecycleManagement, synthetics, upgradeAssistant, ux, kibanaOverview | - | | | spaces, savedObjectsManagement | - | | | spaces, savedObjectsManagement | - | | | spaces, ml, canvas, osquery | - | | | actions, ml, savedObjectsTagging, enterpriseSearch | - | -| | dataViewManagement | - | -| | dataViewManagement | - | | | canvas | - | | | canvas | - | | | canvas | - | @@ -68,8 +66,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | canvas | - | | | canvas | - | | | canvas | - | +| | dataViewManagement | - | +| | dataViewManagement | - | | | enterpriseSearch | - | -| | console, @kbn/core-elasticsearch-server-internal | - | +| | console, @kbn/core-elasticsearch-server-internal, @kbn/core-plugins-server-internal | - | +| | @kbn/core-plugins-server-internal | - | | | spaces, security, alerting | 8.8.0 | | | spaces, security, actions, alerting, ml, remoteClusters, graph, indexLifecycleManagement, mapsEms, painlessLab, rollup, searchprofiler, securitySolution, snapshotRestore, transform, upgradeAssistant | 8.8.0 | | | embeddable, discover, presentationUtil, dashboard, graph | 8.8.0 | @@ -88,6 +89,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | apm | 8.8.0 | | | security | 8.8.0 | | | mapsEms | 8.8.0 | +| | @kbn/core-plugins-server-internal | 8.8.0 | | | ml, @kbn/core-http-browser-internal | 8.8.0 Note to maintainers: when looking at usages, mind that typical use could be inside a `catch` block, @@ -142,8 +144,6 @@ Safe to remove. | | reporting | | | reporting | | | taskManager | -| | core | -| | core | | | @kbn/storybook | | | @kbn/core-application-browser | | | @kbn/core-application-browser | @@ -154,6 +154,8 @@ Safe to remove. | | @kbn/core-injected-metadata-browser | | | @kbn/core-injected-metadata-browser | | | @kbn/core-metrics-server | +| | @kbn/core-plugins-server | +| | @kbn/core-plugins-server | | | @kbn/core-saved-objects-common | | | @kbn/core-saved-objects-common | | | @kbn/core-saved-objects-server | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index fc37544ad5717..a0c2c750783b4 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -70,6 +70,16 @@ so TS and code-reference navigation might not highlight them. | +## @kbn/core-plugins-server-internal + +| Deprecated API | Reference location(s) | Remove By | +| ---------------|-----------|-----------| +| | [plugin_context.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts#:~:text=legacy), [plugin_context.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts#:~:text=legacy) | - | +| | [plugin.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin.ts#:~:text=AsyncPlugin), [plugin.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin.ts#:~:text=AsyncPlugin) | 8.8.0 | +| | [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs), [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs), [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs), [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs), [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs), [plugin_manifest_parser.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts#:~:text=extraPublicDirs) | - | + + + ## @kbn/core-saved-objects-migration-server-internal | Deprecated API | Reference location(s) | Remove By | @@ -129,9 +139,9 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [use_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/hooks/use_data.ts#:~:text=title), [full_time_range_selector_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector_service.ts#:~:text=title), [explain_log_rate_spikes_analysis.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx#:~:text=title), [log_categorization_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx#:~:text=title), [use_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/hooks/use_data.ts#:~:text=title), [full_time_range_selector_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector_service.ts#:~:text=title), [explain_log_rate_spikes_analysis.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx#:~:text=title), [log_categorization_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx#:~:text=title) | - | -| | [use_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/hooks/use_data.ts#:~:text=title), [full_time_range_selector_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector_service.ts#:~:text=title), [explain_log_rate_spikes_analysis.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx#:~:text=title), [log_categorization_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx#:~:text=title), [use_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/hooks/use_data.ts#:~:text=title), [full_time_range_selector_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector_service.ts#:~:text=title), [explain_log_rate_spikes_analysis.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx#:~:text=title), [log_categorization_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx#:~:text=title) | - | -| | [use_data.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/hooks/use_data.ts#:~:text=title), [full_time_range_selector_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector_service.ts#:~:text=title), [explain_log_rate_spikes_analysis.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx#:~:text=title), [log_categorization_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx#:~:text=title) | - | +| | [log_categorization_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx#:~:text=title), [log_categorization_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx#:~:text=title) | - | +| | [log_categorization_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx#:~:text=title), [log_categorization_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx#:~:text=title) | - | +| | [log_categorization_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx#:~:text=title) | - | @@ -170,6 +180,9 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| +| | [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title) | - | +| | [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title) | - | +| | [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title), [data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/services/kibana/data_views.ts#:~:text=title) | - | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts#:~:text=context), [embeddable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts#:~:text=context), [esdocs.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts#:~:text=context), [escount.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/escount.ts#:~:text=context), [filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/common/functions/filters.ts#:~:text=context), [neq.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/common/neq.ts#:~:text=context), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts#:~:text=context) | - | | | [setup_expressions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/public/setup_expressions.ts#:~:text=getFunction) | - | | | [functions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/functions/functions.ts#:~:text=getFunctions), [functions.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/functions/functions.ts#:~:text=getFunctions), [functions.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/canvas/server/routes/functions/functions.test.ts#:~:text=getFunctions) | - | @@ -532,12 +545,11 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| | | [global_sync.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts#:~:text=syncQueryStateWithUrl), [global_sync.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts#:~:text=syncQueryStateWithUrl) | - | -| | [kibana_services.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/kibana_services.ts#:~:text=indexPatterns) | - | -| | [es_geo_grid_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx#:~:text=title), [scaling_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx#:~:text=title), [top_hits_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title)+ 14 more | - | +| | [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=title), [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=title) | - | | | [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit) | - | -| | [es_geo_grid_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx#:~:text=title), [scaling_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx#:~:text=title), [top_hits_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title)+ 14 more | - | +| | [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=title), [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=title) | - | | | [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit) | - | -| | [es_geo_grid_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx#:~:text=title), [scaling_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx#:~:text=title), [top_hits_form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=title)+ 2 more | - | +| | [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=title) | - | | | [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit) | - | | | [render_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/render_app.tsx#:~:text=onAppLeave), [map_app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx#:~:text=onAppLeave), [map_page.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/map_page.tsx#:~:text=onAppLeave) | 8.8.0 | | | [saved_object_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/server/saved_objects/saved_object_migrations.ts#:~:text=warning) | 8.8.0 | @@ -589,9 +601,9 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title), [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title) | - | -| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title), [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title) | - | -| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title) | - | +| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title), [sample_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts#:~:text=title), [sample_attribute_kpi.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts#:~:text=title), [sample_attribute_with_reference_lines.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts#:~:text=title), [test_formula_metric_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title)+ 14 more | - | +| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title), [sample_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts#:~:text=title), [sample_attribute_kpi.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts#:~:text=title), [sample_attribute_with_reference_lines.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts#:~:text=title), [test_formula_metric_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title)+ 14 more | - | +| | [observability_data_views.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts#:~:text=title), [report_definition_field.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/report_definition_field.tsx#:~:text=title), [use_filter_values.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/use_filter_values.ts#:~:text=title), [filter_value_btn.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_value_btn.tsx#:~:text=title), [sample_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts#:~:text=title), [sample_attribute_kpi.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts#:~:text=title), [sample_attribute_with_reference_lines.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts#:~:text=title), [test_formula_metric_attribute.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title), [single_metric_attributes.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts#:~:text=title)+ 2 more | - | | | [use_discover_link.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_discover_link.tsx#:~:text=indexPatternId) | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability/public/application/index.tsx#:~:text=RedirectAppLinks) | - | @@ -713,13 +725,13 @@ so TS and code-reference navigation might not highlight them. | | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | +| | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | | | [disable_ui_capabilities.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts#:~:text=requiredRoles) | 8.8.0 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 | -| | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | -| | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | +| | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | +| | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | | | [disable_ui_capabilities.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts#:~:text=requiredRoles) | 8.8.0 This is relied on by the reporting feature, and should be removed once reporting @@ -739,23 +751,23 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.ts#:~:text=create) | - | -| | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch) | - | +| | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts#:~:text=create) | - | +| | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch) | - | | | [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=indexPatterns), [dependencies_start_mock.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts#:~:text=indexPatterns) | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title), [validators.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx#:~:text=title)+ 18 more | - | -| | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.ts#:~:text=create) | - | -| | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts#:~:text=fetch) | - | +| | [wrap_search_source_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts#:~:text=create) | - | +| | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts#:~:text=options) | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title), [validators.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx#:~:text=title)+ 18 more | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/containers/source/index.tsx#:~:text=title), [utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts#:~:text=title), [get_es_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts#:~:text=title), [middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts#:~:text=title), [get_query_filter.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts#:~:text=title), [index_pattern.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts#:~:text=title), [utils.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/alerts_actions/utils.test.ts#:~:text=title), [validators.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts#:~:text=title), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx#:~:text=title)+ 4 more | - | -| | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 5 more | 8.8.0 | -| | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 5 more | 8.8.0 | +| | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 6 more | 8.8.0 | +| | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 6 more | 8.8.0 | | | [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts#:~:text=license%24) | 8.8.0 | -| | [request_context_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=authc), [request_context_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=authc), [create_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts#:~:text=authc), [delete_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts#:~:text=authc), [finalize_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts#:~:text=authc), [open_close_signals_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts#:~:text=authc), [preview_rules_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts#:~:text=authc), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts#:~:text=authc) | - | +| | [request_context_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=authc), [request_context_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=authc), [route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts#:~:text=authc), [create_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts#:~:text=authc), [delete_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts#:~:text=authc), [finalize_signals_migration_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts#:~:text=authc), [open_close_signals_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts#:~:text=authc), [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/timeline/utils/common.ts#:~:text=authc) | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/index.tsx#:~:text=onAppLeave), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/plugin.tsx#:~:text=onAppLeave) | 8.8.0 | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx#:~:text=AppLeaveHandler), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx#:~:text=AppLeaveHandler), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/types.ts#:~:text=AppLeaveHandler), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/types.ts#:~:text=AppLeaveHandler), [routes.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/routes.tsx#:~:text=AppLeaveHandler), [routes.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/routes.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler), [use_timeline_save_prompt.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts#:~:text=AppLeaveHandler)+ 1 more | 8.8.0 | -| | [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes) | - | -| | [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes) | - | +| | [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes) | - | +| | [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_types.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes), [legacy_migrations.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.ts#:~:text=SavedObjectAttributes) | - | @@ -870,7 +882,6 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [query_string_input.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/query_string_input/query_string_input.tsx#:~:text=indexPatterns) | - | | | [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=title), [fetch_index_patterns.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/query_string_input/fetch_index_patterns.ts#:~:text=title), [change_dataview.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx#:~:text=title), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=title), [fetch_index_patterns.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/query_string_input/fetch_index_patterns.ts#:~:text=title), [change_dataview.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx#:~:text=title) | - | | | [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=title), [fetch_index_patterns.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/query_string_input/fetch_index_patterns.ts#:~:text=title), [change_dataview.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx#:~:text=title), [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=title), [fetch_index_patterns.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/query_string_input/fetch_index_patterns.ts#:~:text=title), [change_dataview.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx#:~:text=title) | - | | | [value_suggestion_provider.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/autocomplete/providers/value_suggestion_provider.ts#:~:text=title), [fetch_index_patterns.ts](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/query_string_input/fetch_index_patterns.ts#:~:text=title), [change_dataview.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx#:~:text=title) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 320e23c36d6e1..5b3583ec8c17d 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -78,6 +78,7 @@ so TS and code-reference navigation might not highlight them. | Note to maintainers: when looking at usages, mind that typical use could be inside a `catch` block, so TS and code-reference navigation might not highlight them. | +| @kbn/core-plugins-server-internal | | [plugin.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin.ts#:~:text=AsyncPlugin), [plugin.ts](https://github.com/elastic/kibana/tree/main/packages/core/plugins/core-plugins-server-internal/src/plugin.ts#:~:text=AsyncPlugin) | 8.8.0 | | @kbn/core-saved-objects-migration-server-internal | | [document_migrator.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.test.ts#:~:text=warning), [migration_logger.ts](https://github.com/elastic/kibana/tree/main/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migration_logger.ts#:~:text=warning) | 8.8.0 | | @kbn/core-apps-browser-internal | | [load_status.ts](https://github.com/elastic/kibana/tree/main/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.ts#:~:text=process), [load_status.ts](https://github.com/elastic/kibana/tree/main/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.ts#:~:text=process), [load_status.ts](https://github.com/elastic/kibana/tree/main/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.ts#:~:text=process), [load_status.ts](https://github.com/elastic/kibana/tree/main/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.ts#:~:text=process), [load_status.ts](https://github.com/elastic/kibana/tree/main/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.ts#:~:text=process), [load_status.ts](https://github.com/elastic/kibana/tree/main/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.ts#:~:text=process), [load_status.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts#:~:text=process), [ops_metrics_collector.ts](https://github.com/elastic/kibana/tree/main/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts#:~:text=process), [get_ops_metrics_log.ts](https://github.com/elastic/kibana/tree/main/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.ts#:~:text=process), [get_ops_metrics_log.test.ts](https://github.com/elastic/kibana/tree/main/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts#:~:text=process)+ 5 more | 8.8.0 | @@ -130,13 +131,13 @@ so TS and code-reference navigation might not highlight them. | | --------|-------|-----------|-----------| | spaces | | [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), [app_authorization.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/app_authorization.ts#:~:text=getKibanaFeatures), [privileges.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.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.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures), [privileges.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts#:~:text=getKibanaFeatures)+ 18 more | 8.8.0 | | spaces | | [spaces_usage_collector.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/plugin.ts#:~:text=license%24), [spaces_usage_collector.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/plugin.ts#:~:text=license%24) | 8.8.0 | -| security | | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | +| security | | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | | security | | [disable_ui_capabilities.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts#:~:text=requiredRoles) | 8.8.0 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 | | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | -| security | | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | +| security | | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | +| security | | [elasticsearch_role.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [role_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/lib/role_utils.ts#:~:text=disabled), [primary_feature_privilege.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/model/primary_feature_privilege.ts#:~:text=disabled), [elasticsearch_role.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/roles/elasticsearch_role.test.ts#:~:text=disabled), [kibana_features.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts#:~:text=disabled), [put.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled), [put_payload.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts#:~:text=disabled) | 8.8.0 | | security | | [disable_ui_capabilities.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security/server/authorization/disable_ui_capabilities.ts#:~:text=requiredRoles) | 8.8.0 This is relied on by the reporting feature, and should be removed once reporting @@ -163,8 +164,8 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| securitySolution | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 5 more | 8.8.0 | -| securitySolution | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 5 more | 8.8.0 | +| securitySolution | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 6 more | 8.8.0 | +| securitySolution | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 6 more | 8.8.0 | | securitySolution | | [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts#:~:text=license%24) | 8.8.0 | | securitySolution | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/index.tsx#:~:text=onAppLeave), [plugin.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/plugin.tsx#:~:text=onAppLeave) | 8.8.0 | | securitySolution | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx#:~:text=AppLeaveHandler), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx#:~:text=AppLeaveHandler), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/types.ts#:~:text=AppLeaveHandler), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/types.ts#:~:text=AppLeaveHandler), [routes.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/routes.tsx#:~:text=AppLeaveHandler), [routes.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/routes.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler), [app.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/app/app.tsx#:~:text=AppLeaveHandler), [use_timeline_save_prompt.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/timeline/use_timeline_save_prompt.ts#:~:text=AppLeaveHandler)+ 1 more | 8.8.0 | diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index d6d54b0e4ab25..45bf1b3ead4e5 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: 2022-10-20 +date: 2022-10-27 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 8c4a6308dd17b..334c593cbc3c7 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -693,7 +693,7 @@ }, ", ", "SearchOutput", - ">" + ", any>" ], "path": "src/plugins/discover/public/embeddable/types.ts", "deprecated": false, diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 61701f6178751..e937879b1ca45 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: 2022-10-20 +date: 2022-10-27 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 5e55cad79b38a..ab8066ed2c8e2 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.devdocs.json b/api_docs/embeddable.devdocs.json index d82dd8df9db25..735d70ccffc5a 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -121,7 +121,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -129,7 +129,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -200,7 +200,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>>" + ", any>, unknown>>" ], "path": "src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.ts", "deprecated": false, @@ -581,7 +581,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>) | undefined" + ", any>, unknown>) | undefined" ], "path": "src/plugins/embeddable/public/lib/attribute_service/attribute_service.tsx", "deprecated": false, @@ -824,7 +824,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - " implements ", + " implements ", { "pluginId": "embeddable", "scope": "public", @@ -953,7 +953,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -961,7 +961,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -1071,7 +1071,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">) => void" + ", any>) => void" ], "path": "src/plugins/embeddable/public/lib/containers/container.ts", "deprecated": false, @@ -1108,7 +1108,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">" + ", any>" ], "path": "src/plugins/embeddable/public/lib/containers/container.ts", "deprecated": false, @@ -1245,7 +1245,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -1253,7 +1253,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(type: string, explicitInput: Partial) => Promise>(type: string, explicitInput: Partial) => Promise = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -1356,7 +1356,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(id: string, newExplicitInput: Partial, newType?: string | undefined) => Promise" + ">(id: string, newExplicitInput: Partial, newType?: string | undefined) => Promise" ], "path": "src/plugins/embeddable/public/lib/containers/container.ts", "deprecated": false, @@ -1540,7 +1540,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>(id: string) => E" + ", any>>(id: string) => E" ], "path": "src/plugins/embeddable/public/lib/containers/container.ts", "deprecated": false, @@ -1678,7 +1678,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>(id: string) => Promise<", + ", any>>(id: string) => Promise<", { "pluginId": "embeddable", "scope": "public", @@ -1766,7 +1766,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(factory: ", + ">(factory: ", { "pluginId": "embeddable", "scope": "public", @@ -2065,7 +2065,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -2073,7 +2073,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -2337,7 +2337,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - " implements ", + " implements ", { "pluginId": "embeddable", "scope": "public", @@ -2345,7 +2345,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - "" + "" ], "path": "src/plugins/embeddable/public/lib/embeddables/embeddable.tsx", "deprecated": false, @@ -2904,7 +2904,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | ", + ", any> | ", { "pluginId": "embeddable", "scope": "public", @@ -2976,7 +2976,7 @@ "label": "render", "description": [], "signature": [ - "(el: HTMLElement) => void" + "(el: HTMLElement) => void | TNode" ], "path": "src/plugins/embeddable/public/lib/embeddables/embeddable.tsx", "deprecated": false, @@ -3222,7 +3222,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | ", + ", any> | ", { "pluginId": "embeddable", "scope": "public", @@ -3606,7 +3606,7 @@ "section": "def-public.EmbeddableRoot", "text": "EmbeddableRoot" }, - " extends React.Component" + " extends React.Component" ], "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", "deprecated": false, @@ -3700,7 +3700,7 @@ "label": "shouldComponentUpdate", "description": [], "signature": [ - "(newProps: Props) => boolean" + "({ embeddable, error, input, loading }: Props, { node }: State) => boolean" ], "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", "deprecated": false, @@ -3711,7 +3711,7 @@ "id": "def-public.EmbeddableRoot.shouldComponentUpdate.$1", "type": "Object", "tags": [], - "label": "newProps", + "label": "{ embeddable, error, input, loading }", "description": [], "signature": [ "Props" @@ -3720,6 +3720,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "embeddable", + "id": "def-public.EmbeddableRoot.shouldComponentUpdate.$2", + "type": "Object", + "tags": [], + "label": "{ node }", + "description": [], + "signature": [ + "State" + ], + "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true } ], "returnComment": [] @@ -4298,7 +4313,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">" + ", React.ReactNode>" ], "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", "deprecated": false, @@ -4419,21 +4434,6 @@ "deprecated": false, "trackAdoption": false, "isRequired": false - }, - { - "parentPluginId": "embeddable", - "id": "def-public.ErrorEmbeddable.Unnamed.$4", - "type": "boolean", - "tags": [], - "label": "compact", - "description": [], - "signature": [ - "boolean" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true } ], "returnComment": [] @@ -4462,39 +4462,7 @@ "label": "render", "description": [], "signature": [ - "(dom: HTMLElement) => void" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "embeddable", - "id": "def-public.ErrorEmbeddable.render.$1", - "type": "Object", - "tags": [], - "label": "dom", - "description": [], - "signature": [ - "HTMLElement" - ], - "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "embeddable", - "id": "def-public.ErrorEmbeddable.destroy", - "type": "Function", - "tags": [], - "label": "destroy", - "description": [], - "signature": [ - "() => void" + "() => JSX.Element" ], "path": "src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx", "deprecated": false, @@ -4606,7 +4574,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -4614,7 +4582,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ", T = ", + ", T = ", "SavedObjectAttributes", ">(def: ", { @@ -4844,7 +4812,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -4901,7 +4869,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">" + ", any>" ], "path": "src/plugins/embeddable/public/lib/embeddables/is_embeddable.ts", "deprecated": false, @@ -5053,7 +5021,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>) => context is ", + ", any>>) => context is ", { "pluginId": "embeddable", "scope": "public", @@ -5085,7 +5053,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -5130,7 +5098,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -5214,7 +5182,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>) => context is ", + ", any>>) => context is ", { "pluginId": "uiActions", "scope": "public", @@ -5266,7 +5234,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -5380,7 +5348,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>) => context is ", + ", any>>) => context is ", { "pluginId": "embeddable", "scope": "public", @@ -5412,7 +5380,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -5457,7 +5425,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>" + ", any>>" ], "path": "src/plugins/embeddable/public/lib/triggers/triggers.ts", "deprecated": false, @@ -5597,7 +5565,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -5605,7 +5573,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -5661,7 +5629,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>>; overlays: ", + ", any>, unknown>>; overlays: ", "OverlayStart", "; notifications: ", "NotificationsStart", @@ -5770,7 +5738,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -5778,7 +5746,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -5862,7 +5830,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>>" + ", any>, unknown>>" ], "path": "src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx", "deprecated": false, @@ -5992,7 +5960,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | undefined, boolean, string | undefined]" + ", any> | undefined, boolean, string | undefined]" ], "path": "src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx", "deprecated": false, @@ -6049,7 +6017,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -6057,7 +6025,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ", ExtraProps = {}>(WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E; } & ExtraProps>) => React.ComponentType<{ embeddable: E; } & ExtraProps>" + ", ExtraProps = {}>(WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E; } & ExtraProps>) => React.ComponentType<{ embeddable: E; } & ExtraProps>" ], "path": "src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx", "deprecated": false, @@ -6374,7 +6342,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">; hideHeader?: boolean | undefined; containerContext?: ", + ", any>; hideHeader?: boolean | undefined; containerContext?: ", { "pluginId": "embeddable", "scope": "public", @@ -7828,7 +7796,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - "" + "" ], "path": "src/plugins/embeddable/public/lib/containers/i_container.ts", "deprecated": false, @@ -7868,7 +7836,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>(id: string) => Promise<", + ", any>>(id: string) => Promise<", { "pluginId": "embeddable", "scope": "public", @@ -8033,7 +8001,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> = ", + ", any> = ", { "pluginId": "embeddable", "scope": "public", @@ -8057,7 +8025,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>(id: string) => E" + ", any>>(id: string) => E" ], "path": "src/plugins/embeddable/public/lib/containers/i_container.ts", "deprecated": false, @@ -8115,7 +8083,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> = ", + ", any> = ", { "pluginId": "embeddable", "scope": "public", @@ -8139,7 +8107,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">>(embeddable: E) => void" + ", any>>(embeddable: E) => void" ], "path": "src/plugins/embeddable/public/lib/containers/i_container.ts", "deprecated": false, @@ -8249,7 +8217,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -8257,7 +8225,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - ">(type: string, explicitInput: Partial) => Promise<", + ">(type: string, explicitInput: Partial) => Promise<", { "pluginId": "embeddable", "scope": "public", @@ -8352,7 +8320,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -8360,7 +8328,7 @@ "section": "def-public.Embeddable", "text": "Embeddable" }, - ">(id: string, newExplicitInput: Partial, newType?: string | undefined) => void" + ">(id: string, newExplicitInput: Partial, newType?: string | undefined) => void" ], "path": "src/plugins/embeddable/public/lib/containers/i_container.ts", "deprecated": false, @@ -8432,7 +8400,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - "" + "" ], "path": "src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts", "deprecated": false, @@ -8869,7 +8837,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | ", + ", any> | ", { "pluginId": "embeddable", "scope": "public", @@ -8911,7 +8879,7 @@ "\nRenders the embeddable at the given node." ], "signature": [ - "(domNode: Element | HTMLElement) => void" + "(domNode: Element | HTMLElement) => void | N" ], "path": "src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts", "deprecated": false, @@ -8933,19 +8901,21 @@ "isRequired": true } ], - "returnComment": [] + "returnComment": [ + "A React node to mount or void in the case when rendering is done without React." + ] }, { "parentPluginId": "embeddable", - "id": "def-public.IEmbeddable.renderError", + "id": "def-public.IEmbeddable.catchError", "type": "Function", "tags": [], - "label": "renderError", + "label": "catchError", "description": [ "\nRenders a custom embeddable error at the given node." ], "signature": [ - "((domNode: Element | HTMLElement, error: ", + "((error: ", { "pluginId": "expressions", "scope": "common", @@ -8953,7 +8923,7 @@ "section": "def-common.ErrorLike", "text": "ErrorLike" }, - ") => () => void) | undefined" + ", domNode: Element | HTMLElement) => N | (() => void)) | undefined" ], "path": "src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts", "deprecated": false, @@ -8961,13 +8931,19 @@ "children": [ { "parentPluginId": "embeddable", - "id": "def-public.IEmbeddable.renderError.$1", + "id": "def-public.IEmbeddable.catchError.$1", "type": "CompoundType", "tags": [], - "label": "domNode", + "label": "error", "description": [], "signature": [ - "Element | HTMLElement" + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ErrorLike", + "text": "ErrorLike" + } ], "path": "src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts", "deprecated": false, @@ -8976,19 +8952,13 @@ }, { "parentPluginId": "embeddable", - "id": "def-public.IEmbeddable.renderError.$2", + "id": "def-public.IEmbeddable.catchError.$2", "type": "CompoundType", "tags": [], - "label": "error", + "label": "domNode", "description": [], "signature": [ - { - "pluginId": "expressions", - "scope": "common", - "docId": "kibExpressionsPluginApi", - "section": "def-common.ErrorLike", - "text": "ErrorLike" - } + "Element | HTMLElement" ], "path": "src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts", "deprecated": false, @@ -8997,7 +8967,7 @@ } ], "returnComment": [ - "A callback that will be called on error destroy." + "A React node or callback that will be called on error destroy." ] }, { @@ -9791,7 +9761,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">; hideHeader?: boolean | undefined; containerContext?: ", + ", any>; hideHeader?: boolean | undefined; containerContext?: ", { "pluginId": "embeddable", "scope": "public", @@ -10116,7 +10086,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -10124,7 +10094,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(id: string, factory: ", + ">(id: string, factory: ", { "pluginId": "embeddable", "scope": "public", @@ -10360,7 +10330,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - " = ", + " = ", { "pluginId": "embeddable", "scope": "public", @@ -10368,7 +10338,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - ">(embeddableFactoryId: string) => ", + ">(embeddableFactoryId: string) => ", { "pluginId": "embeddable", "scope": "public", @@ -10456,7 +10426,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">, unknown>>" + ", any>, unknown>>" ], "path": "src/plugins/embeddable/public/plugin.tsx", "deprecated": false, @@ -10496,7 +10466,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - ">; hideHeader?: boolean | undefined; containerContext?: ", + ", any>; hideHeader?: boolean | undefined; containerContext?: ", { "pluginId": "embeddable", "scope": "public", diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 0dd00809e2877..ef416f5b3c1ee 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; @@ -21,7 +21,7 @@ Contact [App Services](https://github.com/orgs/elastic/teams/kibana-app-services | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 512 | 0 | 412 | 4 | +| 510 | 0 | 410 | 4 | ## Client diff --git a/api_docs/embeddable_enhanced.devdocs.json b/api_docs/embeddable_enhanced.devdocs.json index 0ec7cfbd87676..6b615629b9469 100644 --- a/api_docs/embeddable_enhanced.devdocs.json +++ b/api_docs/embeddable_enhanced.devdocs.json @@ -43,7 +43,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> ? E : never>" + ", any> ? E : never>" ], "path": "x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.ts", "deprecated": false, @@ -112,7 +112,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> & { enhancements: { dynamicActions: ", + ", any> & { enhancements: { dynamicActions: ", { "pluginId": "uiActionsEnhanced", "scope": "public", @@ -273,7 +273,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | undefined; }>[]" + ", any> | undefined; }>[]" ], "path": "x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts", "deprecated": false, diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index cc93dd117ccb1..9d529653959bc 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: 2022-10-20 +date: 2022-10-27 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 c1d7bf09b9d1f..dcf224a87f820 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: 2022-10-20 +date: 2022-10-27 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 918925909b18c..6affa8cf4a397 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 483d97ab9c62e..099d8c9d85382 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index e7b47eb46b459..726d0b7f09340 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.devdocs.json b/api_docs/event_log.devdocs.json index 8a4cb8d6f21ff..6018838914f57 100644 --- a/api_docs/event_log.devdocs.json +++ b/api_docs/event_log.devdocs.json @@ -1312,7 +1312,7 @@ "label": "data", "description": [], "signature": [ - "(Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined)[]" + "(Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined)[]" ], "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", "deprecated": false, @@ -1332,7 +1332,7 @@ "label": "IEvent", "description": [], "signature": [ - "DeepPartial | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}>>> | undefined" + "DeepPartial | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}>>> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, @@ -1347,7 +1347,7 @@ "label": "IValidatedEvent", "description": [], "signature": [ - "Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined" + "Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; uuid?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; outcome?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; uuid?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 3fdd39f12e552..2a6fa718a80ee 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index f441cb64c6c2e..e7a98f24768c0 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: 2022-10-20 +date: 2022-10-27 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 7f6a45e660c15..dbe7df0dfc637 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: 2022-10-20 +date: 2022-10-27 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 76181e4621e3c..0d45f8b3f48dc 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: 2022-10-20 +date: 2022-10-27 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 8f162e409b123..abcff3f3c350a 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: 2022-10-20 +date: 2022-10-27 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 58da6f6098120..a2b9798a1b2f4 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: 2022-10-20 +date: 2022-10-27 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 abc85bd558222..22901ca9906d9 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: 2022-10-20 +date: 2022-10-27 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 02213c68db14c..19f6b242c922b 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: 2022-10-20 +date: 2022-10-27 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 6032cfe7da671..8c19667e6c802 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: 2022-10-20 +date: 2022-10-27 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 0db7950f1d91d..41aef8c5baa03 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: 2022-10-20 +date: 2022-10-27 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 ea00ede6222e9..3f810f773ab8b 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: 2022-10-20 +date: 2022-10-27 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 d45fe1df7e3be..99bc6c657949a 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: 2022-10-20 +date: 2022-10-27 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 c27c5c079bc40..6378f837dd644 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: 2022-10-20 +date: 2022-10-27 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 e445e325a6fde..7a065b0d41bc5 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.devdocs.json b/api_docs/expressions.devdocs.json index 651bca99eca7c..8888cbfd994f0 100644 --- a/api_docs/expressions.devdocs.json +++ b/api_docs/expressions.devdocs.json @@ -634,7 +634,15 @@ }, "[]>) => ", "Observable", - ">" + " | ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueError", + "text": "ExpressionValueError" + }, + ">" ], "path": "src/plugins/expressions/common/execution/execution.ts", "deprecated": false, @@ -13696,7 +13704,15 @@ }, "[]>) => ", "Observable", - ">" + " | ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueError", + "text": "ExpressionValueError" + }, + ">" ], "path": "src/plugins/expressions/common/execution/execution.ts", "deprecated": false, @@ -16105,13 +16121,7 @@ "text": "ExpressionsServerPlugin" }, " implements ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, + "Plugin", "<", { "pluginId": "expressions", @@ -16176,13 +16186,7 @@ "label": "context", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, + "PluginInitializerContext", "" ], "path": "src/plugins/expressions/server/plugin.ts", @@ -16202,13 +16206,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", ") => ", { "pluginId": "expressions", @@ -16230,13 +16228,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "" ], "path": "src/plugins/expressions/server/plugin.ts", @@ -16256,13 +16248,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ") => ", { "pluginId": "expressions", @@ -16284,13 +16270,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], "path": "src/plugins/expressions/server/plugin.ts", "deprecated": false, @@ -22527,7 +22507,15 @@ }, "[]>) => ", "Observable", - ">" + " | ", + { + "pluginId": "expressions", + "scope": "common", + "docId": "kibExpressionsPluginApi", + "section": "def-common.ExpressionValueError", + "text": "ExpressionValueError" + }, + ">" ], "path": "src/plugins/expressions/common/execution/execution.ts", "deprecated": false, diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index f3777ecfaf65b..5c07ea40103d3 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: 2022-10-20 +date: 2022-10-27 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 cbbe75fe20fa4..4214e7c762f0a 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; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; privilegeGroups: readonly Readonly<{ groupType: ", + "Readonly<{ id: string; name: string; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; requireAllSpaces?: boolean | undefined; privilegesTooltip?: string | undefined; privilegeGroups: readonly Readonly<{ groupType: ", { "pluginId": "features", "scope": "common", @@ -372,6 +372,10 @@ "plugin": "security", "path": "x-pack/plugins/security/server/routes/authorization/roles/put.test.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts" @@ -817,6 +821,38 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "features", + "id": "def-public.SubFeatureConfig.requireAllSpaces", + "type": "CompoundType", + "tags": [], + "label": "requireAllSpaces", + "description": [ + "\nWhether or not this privilege should only be granted to `All Spaces *`. Should be used for features that do not\nsupport Spaces. Defaults to `false`." + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/features/common/sub_feature.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "features", + "id": "def-public.SubFeatureConfig.privilegesTooltip", + "type": "string", + "tags": [], + "label": "privilegesTooltip", + "description": [ + "\nOptional message to display on the Role Management screen when configuring permissions for this feature." + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/features/common/sub_feature.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "features", "id": "def-public.SubFeatureConfig.privilegeGroups", @@ -1185,7 +1221,7 @@ "label": "config", "description": [], "signature": [ - "Readonly<{ id: string; name: string; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; privilegeGroups: readonly Readonly<{ groupType: ", + "Readonly<{ id: string; name: string; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; requireAllSpaces?: boolean | undefined; privilegesTooltip?: string | undefined; privilegeGroups: readonly Readonly<{ groupType: ", { "pluginId": "features", "scope": "common", @@ -1680,6 +1716,10 @@ "plugin": "security", "path": "x-pack/plugins/security/server/routes/authorization/roles/put.test.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts" @@ -2868,7 +2908,7 @@ "label": "config", "description": [], "signature": [ - "Readonly<{ id: string; name: string; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; privilegeGroups: readonly Readonly<{ groupType: ", + "Readonly<{ id: string; name: string; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; 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; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }> | null; subFeatures?: readonly Readonly<{ name: string; requireAllSpaces?: boolean | undefined; privilegesTooltip?: string | undefined; privilegeGroups: readonly Readonly<{ groupType: ", { "pluginId": "features", "scope": "common", @@ -3118,7 +3158,7 @@ "label": "config", "description": [], "signature": [ - "Readonly<{ name: string; privilegeGroups: readonly Readonly<{ groupType: ", + "Readonly<{ name: string; requireAllSpaces?: boolean | undefined; privilegesTooltip?: string | undefined; privilegeGroups: readonly Readonly<{ groupType: ", { "pluginId": "features", "scope": "common", @@ -3169,6 +3209,17 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "features", + "id": "def-common.SubFeature.requireAllSpaces", + "type": "boolean", + "tags": [], + "label": "requireAllSpaces", + "description": [], + "path": "x-pack/plugins/features/common/sub_feature.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "features", "id": "def-common.SubFeature.toRaw", @@ -3177,7 +3228,7 @@ "label": "toRaw", "description": [], "signature": [ - "() => { name: string; privilegeGroups: readonly Readonly<{ groupType: ", + "() => { name: string; requireAllSpaces?: boolean | undefined; privilegesTooltip?: string | undefined; privilegeGroups: readonly Readonly<{ groupType: ", { "pluginId": "features", "scope": "common", @@ -3474,6 +3525,10 @@ "plugin": "security", "path": "x-pack/plugins/security/server/routes/authorization/roles/put.test.ts" }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts" + }, { "plugin": "security", "path": "x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts" @@ -3919,6 +3974,38 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "features", + "id": "def-common.SubFeatureConfig.requireAllSpaces", + "type": "CompoundType", + "tags": [], + "label": "requireAllSpaces", + "description": [ + "\nWhether or not this privilege should only be granted to `All Spaces *`. Should be used for features that do not\nsupport Spaces. Defaults to `false`." + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/features/common/sub_feature.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "features", + "id": "def-common.SubFeatureConfig.privilegesTooltip", + "type": "string", + "tags": [], + "label": "privilegesTooltip", + "description": [ + "\nOptional message to display on the Role Management screen when configuring permissions for this feature." + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/features/common/sub_feature.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "features", "id": "def-common.SubFeatureConfig.privilegeGroups", diff --git a/api_docs/features.mdx b/api_docs/features.mdx index b57ca485dacbe..d0d910454e88a 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 222 | 0 | 95 | 2 | +| 227 | 0 | 96 | 2 | ## Client diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index cef8fba31f3aa..5d5c149caa585 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index c6e65acd0c2e1..35d78a391abf5 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.devdocs.json b/api_docs/files.devdocs.json index 520b91977324a..7388e47d5ca19 100644 --- a/api_docs/files.devdocs.json +++ b/api_docs/files.devdocs.json @@ -3,6 +3,54 @@ "client": { "classes": [], "functions": [ + { + "parentPluginId": "files", + "id": "def-public.FilePicker", + "type": "Function", + "tags": [], + "label": "FilePicker", + "description": [], + "signature": [ + "(props: ", + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + ") => JSX.Element" + ], + "path": "x-pack/plugins/files/public/components/file_picker/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.FilePicker.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + "" + ], + "path": "x-pack/plugins/files/public/components/file_picker/index.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "files", "id": "def-public.FilesContext", @@ -11,7 +59,7 @@ "label": "FilesContext", "description": [], "signature": [ - "({ children }: { children?: React.ReactNode; }) => JSX.Element" + "({ client, children }: React.PropsWithChildren) => JSX.Element" ], "path": "x-pack/plugins/files/public/components/context.tsx", "deprecated": false, @@ -20,12 +68,12 @@ { "parentPluginId": "files", "id": "def-public.FilesContext.$1", - "type": "Object", + "type": "CompoundType", "tags": [], - "label": "{ children }", + "label": "{ client, children }", "description": [], "signature": [ - "{ children?: React.ReactNode; }" + "React.PropsWithChildren" ], "path": "x-pack/plugins/files/public/components/context.tsx", "deprecated": false, @@ -163,7 +211,7 @@ "\nCreate a new file object with the provided metadata.\n" ], "signature": [ - "(args: Readonly<{ meta?: Readonly<{} & {}> | undefined; alt?: string | undefined; mimeType?: string | undefined; } & { name: string; }> & { kind: string; }) => Promise<{ file: ", + "(args: Readonly<{ meta?: Readonly<{} & {}> | undefined; alt?: string | undefined; mimeType?: string | undefined; } & { name: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -188,7 +236,7 @@ "- create file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -206,7 +254,7 @@ "\nDelete a file object and all associated share and content objects.\n" ], "signature": [ - "(args: Readonly<{} & { id: string; }> & { kind: string; }) => Promise<{ ok: true; }>" + "(args: Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ ok: true; }>" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -223,7 +271,7 @@ "- delete file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -241,7 +289,7 @@ "\nGet a file object by ID.\n" ], "signature": [ - "(args: Readonly<{} & { id: string; }> & { kind: string; }) => Promise<{ file: ", + "(args: Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -266,7 +314,7 @@ "- get file by ID args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -284,7 +332,7 @@ "\nList all file objects, of a given {@link FileKind}.\n" ], "signature": [ - "(args: Readonly<{ name?: string | string[] | undefined; status?: string | string[] | undefined; meta?: Readonly<{} & {}> | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}> & { kind: string; }) => Promise<{ files: ", + "(args: Readonly<{ name?: string | string[] | undefined; status?: string | string[] | undefined; meta?: Readonly<{} & {}> | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ files: ", { "pluginId": "files", "scope": "common", @@ -309,7 +357,7 @@ "- list files args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -327,7 +375,7 @@ "\nUpdate a set of of metadata values of the file object.\n" ], "signature": [ - "(args: Readonly<{ name?: string | undefined; meta?: Readonly<{} & {}> | undefined; alt?: string | undefined; } & {}> & Readonly<{} & { id: string; }> & { kind: string; }) => Promise<{ file: ", + "(args: Readonly<{ name?: string | undefined; meta?: Readonly<{} & {}> | undefined; alt?: string | undefined; } & {}> & Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -352,7 +400,7 @@ "- update file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -406,7 +454,7 @@ "\nStream a download of the file object's content.\n" ], "signature": [ - "(args: Readonly<{ fileName?: string | undefined; } & { id: string; }> & { kind: string; }) => Promise" + "(args: Readonly<{ fileName?: string | undefined; } & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -423,7 +471,7 @@ "- download file args" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -495,7 +543,7 @@ "\nShare a file by creating a new file share instance.\n" ], "signature": [ - "(args: Readonly<{ name?: string | undefined; validUntil?: number | undefined; } & {}> & Readonly<{} & { fileId: string; }> & { kind: string; }) => Promise<", + "(args: Readonly<{ name?: string | undefined; validUntil?: number | undefined; } & {}> & Readonly<{} & { fileId: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<", { "pluginId": "files", "scope": "common", @@ -520,7 +568,7 @@ "- File share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -538,7 +586,7 @@ "\nDelete a file share instance.\n" ], "signature": [ - "(args: Readonly<{} & { id: string; }> & { kind: string; }) => Promise<{ ok: true; }>" + "(args: Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ ok: true; }>" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -555,7 +603,7 @@ "- File unshare arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -573,7 +621,7 @@ "\nGet a file share instance.\n" ], "signature": [ - "(args: Readonly<{} & { id: string; }> & { kind: string; }) => Promise<{ share: ", + "(args: Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ share: ", { "pluginId": "files", "scope": "common", @@ -598,7 +646,7 @@ "- Get file share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -616,7 +664,7 @@ "\nList all file shares. Optionally scoping to a specific\nfile.\n" ], "signature": [ - "(args: Readonly<{ page?: number | undefined; perPage?: number | undefined; forFileId?: string | undefined; } & {}> & { kind: string; }) => Promise<{ shares: ", + "(args: Readonly<{ page?: number | undefined; perPage?: number | undefined; forFileId?: string | undefined; } & {}> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }) => Promise<{ shares: ", { "pluginId": "files", "scope": "common", @@ -641,7 +689,7 @@ "- Get file share arguments" ], "signature": [ - "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { kind: string; } & ExtraArgs" + "E[\"inputs\"][\"body\"] & E[\"inputs\"][\"params\"] & E[\"inputs\"][\"query\"] & { abortSignal?: AbortSignal | undefined; } & { kind: string; } & ExtraArgs" ], "path": "x-pack/plugins/files/public/types.ts", "deprecated": false, @@ -896,29 +944,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "files", - "id": "def-public.Props.client", - "type": "Object", - "tags": [], - "label": "client", - "description": [ - "\nA files client that will be used process uploads." - ], - "signature": [ - { - "pluginId": "files", - "scope": "public", - "docId": "kibFilesPluginApi", - "section": "def-public.FilesClient", - "text": "FilesClient" - }, - "" - ], - "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "files", "id": "def-public.Props.allowClear", @@ -969,6 +994,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "files", + "id": "def-public.Props.fullWidth", + "type": "CompoundType", + "tags": [], + "label": "fullWidth", + "description": [ + "\nWhether to display the file picker with width 100%;" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "files", "id": "def-public.Props.allowRepeatedUploads", @@ -987,6 +1028,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "files", + "id": "def-public.Props.initialPromptText", + "type": "string", + "tags": [], + "label": "initialPromptText", + "description": [ + "\nThe initial text prompt" + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "files", "id": "def-public.Props.onDone", @@ -1054,6 +1111,151 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "files", + "id": "def-public.Props.compressed", + "type": "CompoundType", + "tags": [ + "default", + "note" + ], + "label": "compressed", + "description": [ + "\nWhether to display the component in it's compact form.\n" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.multiple", + "type": "CompoundType", + "tags": [ + "default" + ], + "label": "multiple", + "description": [ + "\nAllow upload more than one file at a time\n" + ], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/files/public/components/upload_file/upload_file.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props", + "type": "Interface", + "tags": [], + "label": "Props", + "description": [], + "signature": [ + { + "pluginId": "files", + "scope": "public", + "docId": "kibFilesPluginApi", + "section": "def-public.Props", + "text": "Props" + }, + "" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.kind", + "type": "Uncategorized", + "tags": [], + "label": "kind", + "description": [ + "\nThe file kind that was passed to the registry." + ], + "signature": [ + "Kind" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "files", + "id": "def-public.Props.onClose", + "type": "Function", + "tags": [], + "label": "onClose", + "description": [ + "\nWill be called when the modal is closed" + ], + "signature": [ + "() => void" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "files", + "id": "def-public.Props.onDone", + "type": "Function", + "tags": [], + "label": "onDone", + "description": [ + "\nWill be called after a user has a selected a set of files" + ], + "signature": [ + "(fileIds: string[]) => void" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "files", + "id": "def-public.Props.onDone.$1", + "type": "Array", + "tags": [], + "label": "fileIds", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "files", + "id": "def-public.Props.pageSize", + "type": "number", + "tags": [], + "label": "pageSize", + "description": [ + "\nThe number of results to show per page." + ], + "signature": [ + "number | undefined" + ], + "path": "x-pack/plugins/files/public/components/file_picker/file_picker.tsx", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1158,7 +1360,7 @@ "\nA files client that is scoped to a specific {@link FileKind}.\n\nMore convenient if you want to re-use the same client for the same file kind\nand not specify the kind every time." ], "signature": [ - "{ create: (arg: Omit | undefined; alt?: string | undefined; mimeType?: string | undefined; } & { name: string; }> & { kind: string; }, \"kind\">) => Promise<{ file: ", + "{ create: (arg: Omit | undefined; alt?: string | undefined; mimeType?: string | undefined; } & { name: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -1166,7 +1368,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; delete: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ ok: true; }>; getById: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ file: ", + "; }>; delete: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ ok: true; }>; getById: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -1174,7 +1376,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; list: (arg?: Omit | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}> & { kind: string; }, \"kind\"> | undefined) => Promise<{ files: ", + "; }>; list: (arg?: Omit | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\"> | undefined) => Promise<{ files: ", { "pluginId": "files", "scope": "common", @@ -1182,7 +1384,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "[]; total: number; }>; update: (arg: Omit | undefined; alt?: string | undefined; } & {}> & Readonly<{} & { id: string; }> & { kind: string; }, \"kind\">) => Promise<{ file: ", + "[]; total: number; }>; update: (arg: Omit | undefined; alt?: string | undefined; } & {}> & Readonly<{} & { id: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ file: ", { "pluginId": "files", "scope": "common", @@ -1190,7 +1392,7 @@ "section": "def-common.FileJSON", "text": "FileJSON" }, - "; }>; upload: (arg: Omit & Readonly<{ selfDestructOnAbort?: boolean | undefined; } & {}> & { body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit; }>; upload: (arg: Omit & Readonly<{ selfDestructOnAbort?: boolean | undefined; } & {}> & { body: unknown; kind: string; abortSignal?: AbortSignal | undefined; contentType?: string | undefined; }, \"kind\">) => Promise<{ ok: true; size: number; }>; download: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise; getDownloadHref: (arg: Omit, \"id\" | \"fileKind\">, \"kind\">) => string; share: (arg: Omit & Readonly<{} & { fileId: string; }> & { kind: string; }, \"kind\">) => Promise<", + ", \"id\" | \"fileKind\">, \"kind\">) => string; share: (arg: Omit & Readonly<{} & { fileId: string; }> & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<", { "pluginId": "files", "scope": "common", @@ -1206,7 +1408,7 @@ "section": "def-common.FileShareJSONWithToken", "text": "FileShareJSONWithToken" }, - ">; unshare: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ ok: true; }>; getShare: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ share: ", + ">; unshare: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ ok: true; }>; getShare: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ share: ", { "pluginId": "files", "scope": "common", @@ -1214,7 +1416,7 @@ "section": "def-common.FileShareJSON", "text": "FileShareJSON" }, - "; }>; listShares: (arg: Omit & { kind: string; }, \"kind\">) => Promise<{ shares: ", + "; }>; listShares: (arg: Omit & { abortSignal?: AbortSignal | undefined; } & { kind: string; }, \"kind\">) => Promise<{ shares: ", { "pluginId": "files", "scope": "common", @@ -1230,7 +1432,7 @@ "section": "def-common.FilesMetrics", "text": "FilesMetrics" }, - ">; publicDownload: (arg: Omit & Readonly<{} & { token: string; }>, \"kind\">) => Promise; find: (arg: Omit | undefined; kind?: string | string[] | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}>, \"kind\">) => Promise<{ files: ", + ">; publicDownload: (arg: Omit & Readonly<{} & { token: string; }> & { abortSignal?: AbortSignal | undefined; }, \"kind\">) => Promise; find: (arg: Omit | undefined; kind?: string | string[] | undefined; extension?: string | string[] | undefined; } & {}> & Readonly<{ page?: number | undefined; perPage?: number | undefined; } & {}> & { abortSignal?: AbortSignal | undefined; }, \"kind\">) => Promise<{ files: ", { "pluginId": "files", "scope": "common", diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 6e771a1243552..a72d27eb13799 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/tea | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 260 | 0 | 14 | 2 | +| 271 | 0 | 18 | 2 | ## Client diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index c2a258d682244..f9525660b4ded 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -8328,7 +8328,7 @@ "label": "status", "description": [], "signature": [ - "\"inactive\" | \"active\"" + "\"active\" | \"inactive\"" ], "path": "x-pack/plugins/fleet/common/types/models/agent_policy.ts", "deprecated": false, diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 087cf7a080abc..a95e99d7f7774 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: 2022-10-20 +date: 2022-10-27 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 f3d93c0b96f28..c102ddbce9f83 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.devdocs.json b/api_docs/guided_onboarding.devdocs.json index 701721e92edd3..2fcc0495ff8d7 100644 --- a/api_docs/guided_onboarding.devdocs.json +++ b/api_docs/guided_onboarding.devdocs.json @@ -6,111 +6,467 @@ "interfaces": [ { "parentPluginId": "guidedOnboarding", - "id": "def-public.GuideState", + "id": "def-public.GuidedOnboardingApi", "type": "Interface", "tags": [], - "label": "GuideState", + "label": "GuidedOnboardingApi", "description": [], - "path": "src/plugins/guided_onboarding/common/types.ts", + "path": "src/plugins/guided_onboarding/public/types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "guidedOnboarding", - "id": "def-public.GuideState.guideId", - "type": "CompoundType", + "id": "def-public.GuidedOnboardingApi.setup", + "type": "Function", "tags": [], - "label": "guideId", + "label": "setup", "description": [], "signature": [ - "\"search\" | \"security\" | \"observability\"" + "(httpClient: ", + "HttpSetup", + ") => void" ], - "path": "src/plugins/guided_onboarding/common/types.ts", + "path": "src/plugins/guided_onboarding/public/types.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.setup.$1", + "type": "Object", + "tags": [], + "label": "httpClient", + "description": [], + "signature": [ + "HttpSetup" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] }, { "parentPluginId": "guidedOnboarding", - "id": "def-public.GuideState.status", - "type": "CompoundType", + "id": "def-public.GuidedOnboardingApi.fetchActiveGuideState$", + "type": "Function", "tags": [], - "label": "status", + "label": "fetchActiveGuideState$", "description": [], "signature": [ - "\"complete\" | \"in_progress\" | \"ready_to_complete\"" + "() => ", + "Observable", + "<", + "GuideState", + " | undefined>" ], - "path": "src/plugins/guided_onboarding/common/types.ts", + "path": "src/plugins/guided_onboarding/public/types.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [], + "returnComment": [] }, { "parentPluginId": "guidedOnboarding", - "id": "def-public.GuideState.isActive", - "type": "CompoundType", + "id": "def-public.GuidedOnboardingApi.fetchAllGuidesState", + "type": "Function", "tags": [], - "label": "isActive", + "label": "fetchAllGuidesState", "description": [], "signature": [ - "boolean | undefined" + "() => Promise<{ state: ", + "GuideState", + "[]; } | undefined>" ], - "path": "src/plugins/guided_onboarding/common/types.ts", + "path": "src/plugins/guided_onboarding/public/types.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [], + "returnComment": [] }, { "parentPluginId": "guidedOnboarding", - "id": "def-public.GuideState.steps", - "type": "Array", + "id": "def-public.GuidedOnboardingApi.updateGuideState", + "type": "Function", "tags": [], - "label": "steps", + "label": "updateGuideState", "description": [], "signature": [ - "GuideStep", - "[]" + "(newState: ", + "GuideState", + ", panelState: boolean) => Promise<{ state: ", + "GuideState", + "; } | undefined>" ], - "path": "src/plugins/guided_onboarding/common/types.ts", + "path": "src/plugins/guided_onboarding/public/types.ts", "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "guidedOnboarding", - "id": "def-public.GuideStep", - "type": "Interface", - "tags": [], - "label": "GuideStep", - "description": [], - "path": "src/plugins/guided_onboarding/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.updateGuideState.$1", + "type": "Object", + "tags": [], + "label": "newState", + "description": [], + "signature": [ + "GuideState" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.updateGuideState.$2", + "type": "boolean", + "tags": [], + "label": "panelState", + "description": [], + "signature": [ + "boolean" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "guidedOnboarding", - "id": "def-public.GuideStep.id", - "type": "CompoundType", + "id": "def-public.GuidedOnboardingApi.activateGuide", + "type": "Function", "tags": [], - "label": "id", + "label": "activateGuide", "description": [], "signature": [ - "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alertsCases\" | \"browse_docs\" | \"search_experience\"" + "(guideId: ", + "GuideId", + ", guide?: ", + "GuideState", + " | undefined) => Promise<{ state: ", + "GuideState", + "; } | undefined>" ], - "path": "src/plugins/guided_onboarding/common/types.ts", + "path": "src/plugins/guided_onboarding/public/types.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.activateGuide.$1", + "type": "CompoundType", + "tags": [], + "label": "guideId", + "description": [], + "signature": [ + "GuideId" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.activateGuide.$2", + "type": "Object", + "tags": [], + "label": "guide", + "description": [], + "signature": [ + "GuideState", + " | undefined" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.completeGuide", + "type": "Function", + "tags": [], + "label": "completeGuide", + "description": [], + "signature": [ + "(guideId: ", + "GuideId", + ") => Promise<{ state: ", + "GuideState", + "; } | undefined>" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.completeGuide.$1", + "type": "CompoundType", + "tags": [], + "label": "guideId", + "description": [], + "signature": [ + "GuideId" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.isGuideStepActive$", + "type": "Function", + "tags": [], + "label": "isGuideStepActive$", + "description": [], + "signature": [ + "(guideId: ", + "GuideId", + ", stepId: ", + "GuideStepIds", + ") => ", + "Observable", + "" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.isGuideStepActive$.$1", + "type": "CompoundType", + "tags": [], + "label": "guideId", + "description": [], + "signature": [ + "GuideId" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.isGuideStepActive$.$2", + "type": "CompoundType", + "tags": [], + "label": "stepId", + "description": [], + "signature": [ + "GuideStepIds" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.startGuideStep", + "type": "Function", + "tags": [], + "label": "startGuideStep", + "description": [], + "signature": [ + "(guideId: ", + "GuideId", + ", stepId: ", + "GuideStepIds", + ") => Promise<{ state: ", + "GuideState", + "; } | undefined>" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.startGuideStep.$1", + "type": "CompoundType", + "tags": [], + "label": "guideId", + "description": [], + "signature": [ + "GuideId" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.startGuideStep.$2", + "type": "CompoundType", + "tags": [], + "label": "stepId", + "description": [], + "signature": [ + "GuideStepIds" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] }, { "parentPluginId": "guidedOnboarding", - "id": "def-public.GuideStep.status", - "type": "CompoundType", + "id": "def-public.GuidedOnboardingApi.completeGuideStep", + "type": "Function", "tags": [], - "label": "status", + "label": "completeGuideStep", "description": [], "signature": [ - "\"complete\" | \"in_progress\" | \"ready_to_complete\" | \"inactive\" | \"active\"" + "(guideId: ", + "GuideId", + ", stepId: ", + "GuideStepIds", + ") => Promise<{ state: ", + "GuideState", + "; } | undefined>" ], - "path": "src/plugins/guided_onboarding/common/types.ts", + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.completeGuideStep.$1", + "type": "CompoundType", + "tags": [], + "label": "guideId", + "description": [], + "signature": [ + "GuideId" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.completeGuideStep.$2", + "type": "CompoundType", + "tags": [], + "label": "stepId", + "description": [], + "signature": [ + "GuideStepIds" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.isGuidedOnboardingActiveForIntegration$", + "type": "Function", + "tags": [], + "label": "isGuidedOnboardingActiveForIntegration$", + "description": [], + "signature": [ + "(integration?: string | undefined) => ", + "Observable", + "" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.isGuidedOnboardingActiveForIntegration$.$1", + "type": "string", + "tags": [], + "label": "integration", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.completeGuidedOnboardingForIntegration", + "type": "Function", + "tags": [], + "label": "completeGuidedOnboardingForIntegration", + "description": [], + "signature": [ + "(integration?: string | undefined) => Promise<{ state: ", + "GuideState", + "; } | undefined>" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.completeGuidedOnboardingForIntegration.$1", + "type": "string", + "tags": [], + "label": "integration", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuidedOnboardingApi.isGuidePanelOpen$", + "type": "Object", + "tags": [], + "label": "isGuidePanelOpen$", + "description": [], + "signature": [ + "Observable", + "" + ], + "path": "src/plugins/guided_onboarding/public/types.ts", "deprecated": false, "trackAdoption": false } @@ -119,38 +475,7 @@ } ], "enums": [], - "misc": [ - { - "parentPluginId": "guidedOnboarding", - "id": "def-public.GuideId", - "type": "Type", - "tags": [], - "label": "GuideId", - "description": [], - "signature": [ - "\"search\" | \"security\" | \"observability\"" - ], - "path": "src/plugins/guided_onboarding/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "guidedOnboarding", - "id": "def-public.GuideStepIds", - "type": "Type", - "tags": [], - "label": "GuideStepIds", - "description": [], - "signature": [ - "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alertsCases\" | \"browse_docs\" | \"search_experience\"" - ], - "path": "src/plugins/guided_onboarding/common/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ], + "misc": [], "objects": [ { "parentPluginId": "guidedOnboarding", @@ -242,7 +567,13 @@ "label": "guidedOnboardingApi", "description": [], "signature": [ - "GuidedOnboardingApi", + { + "pluginId": "guidedOnboarding", + "scope": "public", + "docId": "kibGuidedOnboardingPluginApi", + "section": "def-public.GuidedOnboardingApi", + "text": "GuidedOnboardingApi" + }, " | undefined" ], "path": "src/plugins/guided_onboarding/public/types.ts", diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 726b6f3c654f4..d2f54cde989c0 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Journey Onboarding](https://github.com/orgs/elastic/teams/platform-onbo | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 19 | 0 | 19 | 3 | +| 36 | 0 | 36 | 1 | ## Client @@ -37,9 +37,6 @@ Contact [Journey Onboarding](https://github.com/orgs/elastic/teams/platform-onbo ### Interfaces -### Consts, variables and types - - ## Server ### Setup diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 2e08125dbf0df..9f8267fbd0bd1 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index ee2c2b3c76c61..1f98919bac6b3 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: 2022-10-20 +date: 2022-10-27 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 ccf82eecd640c..e8de84f565030 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 2a13ce12dafe2..11b21e8dbb6b2 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 3d47e4cc70f9a..cdb258cc847e0 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 9746756f8ab42..abfc607808752 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index c26bdc0a8083d..9847dee0009a6 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 9e282340ca79b..f4cc1b14a9826 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 7799cbb0da0da..ca93de01e5337 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 6a0fe5ecba9c1..e2af46fb3bcda 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index ec29231f78d09..7b2d8e07b4bf4 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index b971dd4478fc0..0f0a36e4824e0 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 6f0d1df3c1bbb..d757d13d6feca 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index 4418c2cf4afea..18c2f4e816e93 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 4ef5ce2ef87d8..b91cea2c602cc 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 84685fd08bd10..34465561f4853 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index d5aa154b832f8..3208aed0747f1 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.mdx +++ b/api_docs/kbn_analytics_shippers_gainsight.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-gainsight title: "@kbn/analytics-shippers-gainsight" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-gainsight plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index c52448ed80232..31e60a33a1c8f 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: 2022-10-20 +date: 2022-10-27 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_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 50629462e2b39..5d386cd8bf5ba 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index e65246d87557b..110beeb3cc918 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index f8e125f5b780f..e46c4819f134d 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 00943cc13ceb5..ad05a61239ed9 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 459755e68f057..40322ae78a1cb 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: 2022-10-20 +date: 2022-10-27 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 01138f069b066..9a2a46ba869ce 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: 2022-10-20 +date: 2022-10-27 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 e356290cd47e1..ceedbfc013c95 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: 2022-10-20 +date: 2022-10-27 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 13abde8e6ce1c..2f1210d1db411 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: 2022-10-20 +date: 2022-10-27 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 6531f9a9c4a1e..dfdd60e0d6821 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 98e8b32d6927b..93a4c258f96e0 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: 2022-10-20 +date: 2022-10-27 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 cd44d6dbd6bcd..bfa20abb792ff 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: 2022-10-20 +date: 2022-10-27 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 029f9987bb411..c1b612cd46ef8 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: 2022-10-20 +date: 2022-10-27 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 8266a971c454a..e45fb544c8e68 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 62f6515643884..4903301355b2c 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 39f44bcf5c4db..130f63eb2329f 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: 2022-10-20 +date: 2022-10-27 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 bcb243ba3411b..c3fcca0bb1258 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: 2022-10-20 +date: 2022-10-27 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 58502ae32a939..9c793c948fe42 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: 2022-10-20 +date: 2022-10-27 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.mdx b/api_docs/kbn_core_analytics_server.mdx index 05b908cc151c2..4603be95b2a58 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: 2022-10-20 +date: 2022-10-27 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 9dc30e9abfb98..0ec328a7695d9 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: 2022-10-20 +date: 2022-10-27 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 7a081daf64743..8efb0ba3b45b5 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: 2022-10-20 +date: 2022-10-27 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 47a8b2bbe3197..264acd0128266 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: 2022-10-20 +date: 2022-10-27 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 c30b1ccd5b390..38471bd9b7509 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: 2022-10-20 +date: 2022-10-27 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 d142c7c8838d6..edd31fd4e8de8 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: 2022-10-20 +date: 2022-10-27 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 d61802c3b00af..8150b0ad7c64e 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: 2022-10-20 +date: 2022-10-27 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 e35b13e80d01b..c6d522c54582a 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: 2022-10-20 +date: 2022-10-27 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 411a78189f813..a4b2f7eb46508 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: 2022-10-20 +date: 2022-10-27 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_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 4369bd9e3ea0c..83c35c9b87678 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: 2022-10-20 +date: 2022-10-27 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 f355006efea46..9281a8d054fd3 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: 2022-10-20 +date: 2022-10-27 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 94cdf4ef4d11d..f1fa0d588eb59 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: 2022-10-20 +date: 2022-10-27 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 1161eda5a59fe..af8da8e505f15 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: 2022-10-20 +date: 2022-10-27 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 265cd3fc774ab..a964a90f20dfa 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: 2022-10-20 +date: 2022-10-27 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 fede42e9d0dd3..34ee6cf57d9ad 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: 2022-10-20 +date: 2022-10-27 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 e8d75ce033da9..e45b8ea94cb52 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: 2022-10-20 +date: 2022-10-27 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 4e5e050a5c0e7..e4438f7d3cea9 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: 2022-10-20 +date: 2022-10-27 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 fb399e5080e6f..ac10e8f1cb1d1 100644 --- a/api_docs/kbn_core_chrome_browser.devdocs.json +++ b/api_docs/kbn_core_chrome_browser.devdocs.json @@ -187,6 +187,50 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-chrome-browser", + "id": "def-common.ChromeGlobalHelpExtensionMenuLink", + "type": "Interface", + "tags": [], + "label": "ChromeGlobalHelpExtensionMenuLink", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-chrome-browser", + "scope": "common", + "docId": "kibKbnCoreChromeBrowserPluginApi", + "section": "def-common.ChromeGlobalHelpExtensionMenuLink", + "text": "ChromeGlobalHelpExtensionMenuLink" + }, + " extends ", + { + "pluginId": "@kbn/core-chrome-browser", + "scope": "common", + "docId": "kibKbnCoreChromeBrowserPluginApi", + "section": "def-common.ChromeHelpExtensionMenuCustomLink", + "text": "ChromeHelpExtensionMenuCustomLink" + } + ], + "path": "packages/core/chrome/core-chrome-browser/src/help_extension.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-chrome-browser", + "id": "def-common.ChromeGlobalHelpExtensionMenuLink.priority", + "type": "number", + "tags": [], + "label": "priority", + "description": [ + "\nHighest priority items are listed at the top of the list of links." + ], + "path": "packages/core/chrome/core-chrome-browser/src/help_extension.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-chrome-browser", "id": "def-common.ChromeHelpExtension", @@ -1882,6 +1926,82 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/core-chrome-browser", + "id": "def-common.ChromeStart.getGlobalHelpExtensionMenuLinks$", + "type": "Function", + "tags": [], + "label": "getGlobalHelpExtensionMenuLinks$", + "description": [ + "\nGet the list of the registered global help extension menu links" + ], + "signature": [ + "() => ", + "Observable", + "<", + { + "pluginId": "@kbn/core-chrome-browser", + "scope": "common", + "docId": "kibKbnCoreChromeBrowserPluginApi", + "section": "def-common.ChromeGlobalHelpExtensionMenuLink", + "text": "ChromeGlobalHelpExtensionMenuLink" + }, + "[]>" + ], + "path": "packages/core/chrome/core-chrome-browser/src/contracts.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-chrome-browser", + "id": "def-common.ChromeStart.registerGlobalHelpExtensionMenuLink", + "type": "Function", + "tags": [], + "label": "registerGlobalHelpExtensionMenuLink", + "description": [ + "\nAppend a global help extension menu link" + ], + "signature": [ + "(globalHelpExtensionMenuLink: ", + { + "pluginId": "@kbn/core-chrome-browser", + "scope": "common", + "docId": "kibKbnCoreChromeBrowserPluginApi", + "section": "def-common.ChromeGlobalHelpExtensionMenuLink", + "text": "ChromeGlobalHelpExtensionMenuLink" + }, + ") => void" + ], + "path": "packages/core/chrome/core-chrome-browser/src/contracts.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-chrome-browser", + "id": "def-common.ChromeStart.registerGlobalHelpExtensionMenuLink.$1", + "type": "Object", + "tags": [], + "label": "globalHelpExtensionMenuLink", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-chrome-browser", + "scope": "common", + "docId": "kibKbnCoreChromeBrowserPluginApi", + "section": "def-common.ChromeGlobalHelpExtensionMenuLink", + "text": "ChromeGlobalHelpExtensionMenuLink" + } + ], + "path": "packages/core/chrome/core-chrome-browser/src/contracts.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "@kbn/core-chrome-browser", "id": "def-common.ChromeStart.getHelpExtension$", @@ -1889,7 +2009,7 @@ "tags": [], "label": "getHelpExtension$", "description": [ - "\nGet an observable of the current custom help conttent" + "\nGet an observable of the current custom help content" ], "signature": [ "() => ", diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index fe100b0920608..2db0226b148d5 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 114 | 0 | 41 | 0 | +| 119 | 0 | 43 | 0 | ## Common diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 7ae7bba520fb9..8879661860eb7 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: 2022-10-20 +date: 2022-10-27 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 bbd49dea4979b..2bf7804911206 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: 2022-10-20 +date: 2022-10-27 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_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 4cf11b93369e3..e72f5003eb85b 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: 2022-10-20 +date: 2022-10-27 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 aeb4e0ae4ecee..893daa641bc41 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: 2022-10-20 +date: 2022-10-27 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 1cf8420a6b97a..96563e8f54e98 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: 2022-10-20 +date: 2022-10-27 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 fcb2097c28cb9..c5b48f2ad3ba4 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: 2022-10-20 +date: 2022-10-27 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 dd981a61e355e..7e7d21fac0e22 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: 2022-10-20 +date: 2022-10-27 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 ade2be4bebe74..bea005a7d4e59 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: 2022-10-20 +date: 2022-10-27 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 e6666ab809257..621351457c9b7 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: 2022-10-20 +date: 2022-10-27 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 f3ccd9d1366e4..5e9c2399d86f7 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: 2022-10-20 +date: 2022-10-27 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 738a3b277d8e0..7dd5e0cc196b8 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: 2022-10-20 +date: 2022-10-27 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 b308c2adcab7b..ce8f26292872b 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: 2022-10-20 +date: 2022-10-27 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 eb72c0bc4dfe9..a4a47dc9b0598 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: 2022-10-20 +date: 2022-10-27 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 02441d501dfbf..82251a0065910 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: 2022-10-20 +date: 2022-10-27 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 591759a1c7d69..e6ba16076c79c 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: 2022-10-20 +date: 2022-10-27 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 2739e7d96b49e..6f1770f9c9235 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: 2022-10-20 +date: 2022-10-27 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 0b1c2b9ad1654..0520f9cd4178c 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: 2022-10-20 +date: 2022-10-27 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 2df63c669ea89..3400d697b5e40 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: 2022-10-20 +date: 2022-10-27 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 756ec32503839..99ac72bc588ed 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: 2022-10-20 +date: 2022-10-27 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 6264f03019292..d98964ff87f6c 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: 2022-10-20 +date: 2022-10-27 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 f5aa12e30b02a..44cb268cc8f87 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: 2022-10-20 +date: 2022-10-27 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 97a8f7840a8dc..b137763f0873c 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: 2022-10-20 +date: 2022-10-27 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 1d0cd0c1bc692..a4983dabb70ad 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: 2022-10-20 +date: 2022-10-27 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 8cdea0c7f0c1b..8231f970b3f6a 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: 2022-10-20 +date: 2022-10-27 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 15dbe3b1d82f8..9417f15d7d4bf 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: 2022-10-20 +date: 2022-10-27 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 6217c28a7f041..e1ef9bf2b29f2 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: 2022-10-20 +date: 2022-10-27 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 5d61440d74bf1..5e4b9da6d9d2d 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: 2022-10-20 +date: 2022-10-27 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 f3938fb47b989..bd889b07d2448 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: 2022-10-20 +date: 2022-10-27 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 0aa76ef83048d..e0b057a2c9c88 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: 2022-10-20 +date: 2022-10-27 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_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index e06d2aee4c7c3..e77c9663bb88f 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: 2022-10-20 +date: 2022-10-27 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 7c0035c2d3eb7..d551bfbb4615e 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: 2022-10-20 +date: 2022-10-27 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 80bc22c6eb865..a7aa82dee2b5d 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: 2022-10-20 +date: 2022-10-27 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 744fde2fb9c04..adff4618167ec 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: 2022-10-20 +date: 2022-10-27 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 ec23789bb2a92..1ffcaeca27d27 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: 2022-10-20 +date: 2022-10-27 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 4478bd912b23b..2b3c79c28cd22 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: 2022-10-20 +date: 2022-10-27 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 61da8e383b619..a9d46143fe6c0 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: 2022-10-20 +date: 2022-10-27 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 ee5068b3ff7ef..c34e11e1431bc 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: 2022-10-20 +date: 2022-10-27 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 67207dd25cbdb..860aa126a5781 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: 2022-10-20 +date: 2022-10-27 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 a5f2b456a30ae..39eac9e75b745 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: 2022-10-20 +date: 2022-10-27 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 1e347c3470883..49698d0b41fbb 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: 2022-10-20 +date: 2022-10-27 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.mdx b/api_docs/kbn_core_http_server.mdx index 6c5e8c178a052..418d2b36dfd4b 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: 2022-10-20 +date: 2022-10-27 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.devdocs.json b/api_docs/kbn_core_http_server_internal.devdocs.json index 1a51d9c4ae675..d7eec9a8625a8 100644 --- a/api_docs/kbn_core_http_server_internal.devdocs.json +++ b/api_docs/kbn_core_http_server_internal.devdocs.json @@ -382,7 +382,7 @@ "label": "compression", "description": [], "signature": [ - "{ enabled: boolean; referrerWhitelist?: string[] | undefined; }" + "{ enabled: boolean; referrerWhitelist?: string[] | undefined; brotli: { enabled: boolean; quality: number; }; }" ], "path": "packages/core/http/core-http-server-internal/src/http_config.ts", "deprecated": false, @@ -759,7 +759,7 @@ "label": "HttpConfigType", "description": [], "signature": [ - "{ readonly uuid?: string | undefined; readonly basePath?: string | undefined; readonly publicBaseUrl?: string | undefined; readonly name: string; readonly host: string; readonly compression: Readonly<{ referrerWhitelist?: string[] | undefined; } & { enabled: boolean; }>; readonly ssl: Readonly<{ key?: string | undefined; certificateAuthorities?: string | string[] | undefined; certificate?: string | undefined; keyPassphrase?: string | undefined; redirectHttpFromPort?: number | undefined; } & { enabled: boolean; keystore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; truststore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; cipherSuites: string[]; supportedProtocols: string[]; clientAuthentication: \"optional\" | \"none\" | \"required\"; }>; readonly port: number; readonly cors: Readonly<{} & { enabled: boolean; allowCredentials: boolean; allowOrigin: string[] | \"*\"[]; }>; readonly autoListen: boolean; readonly shutdownTimeout: moment.Duration; readonly securityResponseHeaders: Readonly<{} & { referrerPolicy: \"origin\" | \"no-referrer\" | \"no-referrer-when-downgrade\" | \"origin-when-cross-origin\" | \"same-origin\" | \"strict-origin\" | \"strict-origin-when-cross-origin\" | \"unsafe-url\" | null; disableEmbedding: boolean; strictTransportSecurity: string | null; xContentTypeOptions: \"nosniff\" | null; permissionsPolicy: string | null; }>; readonly customResponseHeaders: Record; readonly maxPayload: ", + "{ readonly uuid?: string | undefined; readonly basePath?: string | undefined; readonly publicBaseUrl?: string | undefined; readonly name: string; readonly host: string; readonly compression: Readonly<{ referrerWhitelist?: string[] | undefined; } & { enabled: boolean; brotli: Readonly<{} & { enabled: boolean; quality: number; }>; }>; readonly ssl: Readonly<{ key?: string | undefined; certificateAuthorities?: string | string[] | undefined; certificate?: string | undefined; keyPassphrase?: string | undefined; redirectHttpFromPort?: number | undefined; } & { enabled: boolean; keystore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; truststore: Readonly<{ path?: string | undefined; password?: string | undefined; } & {}>; cipherSuites: string[]; supportedProtocols: string[]; clientAuthentication: \"optional\" | \"none\" | \"required\"; }>; readonly port: number; readonly cors: Readonly<{} & { enabled: boolean; allowCredentials: boolean; allowOrigin: string[] | \"*\"[]; }>; readonly autoListen: boolean; readonly shutdownTimeout: moment.Duration; readonly securityResponseHeaders: Readonly<{} & { referrerPolicy: \"origin\" | \"no-referrer\" | \"no-referrer-when-downgrade\" | \"origin-when-cross-origin\" | \"same-origin\" | \"strict-origin\" | \"strict-origin-when-cross-origin\" | \"unsafe-url\" | null; disableEmbedding: boolean; strictTransportSecurity: string | null; xContentTypeOptions: \"nosniff\" | null; permissionsPolicy: string | null; }>; readonly customResponseHeaders: Record; readonly maxPayload: ", "ByteSizeValue", "; readonly rewriteBasePath: boolean; readonly keepaliveTimeout: number; readonly socketTimeout: number; readonly xsrf: Readonly<{} & { disableProtection: boolean; allowlist: string[]; }>; readonly requestId: Readonly<{} & { allowFromAnyIp: boolean; ipAllowlist: string[]; }>; }" ], diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index a01bae8661e79..8c4e8dcbecf2a 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: 2022-10-20 +date: 2022-10-27 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 2efc3f42097d9..bcd232b3dedf8 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: 2022-10-20 +date: 2022-10-27 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 3a87234600ca4..ba6a0d53685ae 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: 2022-10-20 +date: 2022-10-27 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 df1dfc488ecf8..1d8acfda2155a 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: 2022-10-20 +date: 2022-10-27 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 c95b92361b0fc..006c15a3fbc7c 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: 2022-10-20 +date: 2022-10-27 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 e994fb4cf6099..1e150ac8f8e60 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: 2022-10-20 +date: 2022-10-27 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 c57fce245fc88..be156b5dfd52d 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: 2022-10-20 +date: 2022-10-27 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.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 4d1e468a6fdf1..f0948fd647d0b 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.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 b63a9403138ce..9924c35a59a11 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: 2022-10-20 +date: 2022-10-27 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 e564870c497f4..6e805256d4a71 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: 2022-10-20 +date: 2022-10-27 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 0e478b532f36b..9170729fab145 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: 2022-10-20 +date: 2022-10-27 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 42379bba304d0..2ec34fc78d756 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: 2022-10-20 +date: 2022-10-27 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 bb96d7fdc5a63..572a23b692621 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: 2022-10-20 +date: 2022-10-27 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.devdocs.json b/api_docs/kbn_core_lifecycle_server.devdocs.json new file mode 100644 index 0000000000000..c49e0d2cda602 --- /dev/null +++ b/api_docs/kbn_core_lifecycle_server.devdocs.json @@ -0,0 +1,602 @@ +{ + "id": "@kbn/core-lifecycle-server", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CorePreboot", + "type": "Interface", + "tags": [], + "label": "CorePreboot", + "description": [ + "\nContext passed to the `setup` method of `preboot` plugins." + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_preboot.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CorePreboot.analytics", + "type": "Object", + "tags": [], + "label": "analytics", + "description": [ + "{@link AnalyticsServicePreboot}" + ], + "signature": [ + "{ optIn: (optInConfig: ", + "OptInConfig", + ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", + "Observable", + "<", + "TelemetryCounter", + ">; registerEventType: (eventTypeOps: ", + "EventTypeOpts", + ") => void; registerShipper: (Shipper: ", + "ShipperClassConstructor", + ", shipperConfig: ShipperConfig, opts?: ", + "RegisterShipperOpts", + " | undefined) => void; registerContextProvider: (contextProviderOpts: ", + "ContextProviderOpts", + ") => void; removeContextProvider: (contextProviderName: string) => void; }" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_preboot.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CorePreboot.elasticsearch", + "type": "Object", + "tags": [], + "label": "elasticsearch", + "description": [ + "{@link ElasticsearchServicePreboot}" + ], + "signature": [ + "ElasticsearchServicePreboot" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_preboot.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CorePreboot.http", + "type": "Object", + "tags": [], + "label": "http", + "description": [ + "{@link HttpServicePreboot}" + ], + "signature": [ + "HttpServicePreboot", + "<", + "RequestHandlerContext", + ">" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_preboot.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CorePreboot.preboot", + "type": "Object", + "tags": [], + "label": "preboot", + "description": [ + "{@link PrebootServicePreboot}" + ], + "signature": [ + "PrebootServicePreboot" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_preboot.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup", + "type": "Interface", + "tags": [], + "label": "CoreSetup", + "description": [ + "\nContext passed to the `setup` method of `standard` plugins.\n" + ], + "signature": [ + { + "pluginId": "@kbn/core-lifecycle-server", + "scope": "server", + "docId": "kibKbnCoreLifecycleServerPluginApi", + "section": "def-server.CoreSetup", + "text": "CoreSetup" + }, + "" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.analytics", + "type": "Object", + "tags": [], + "label": "analytics", + "description": [ + "{@link AnalyticsServiceSetup}" + ], + "signature": [ + "{ optIn: (optInConfig: ", + "OptInConfig", + ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", + "Observable", + "<", + "TelemetryCounter", + ">; registerEventType: (eventTypeOps: ", + "EventTypeOpts", + ") => void; registerShipper: (Shipper: ", + "ShipperClassConstructor", + ", shipperConfig: ShipperConfig, opts?: ", + "RegisterShipperOpts", + " | undefined) => void; registerContextProvider: (contextProviderOpts: ", + "ContextProviderOpts", + ") => void; removeContextProvider: (contextProviderName: string) => void; }" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.capabilities", + "type": "Object", + "tags": [], + "label": "capabilities", + "description": [ + "{@link CapabilitiesSetup}" + ], + "signature": [ + "CapabilitiesSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.docLinks", + "type": "Object", + "tags": [], + "label": "docLinks", + "description": [ + "{@link DocLinksServiceSetup}" + ], + "signature": [ + "DocLinksServiceSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.elasticsearch", + "type": "Object", + "tags": [], + "label": "elasticsearch", + "description": [ + "{@link ElasticsearchServiceSetup}" + ], + "signature": [ + "ElasticsearchServiceSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.executionContext", + "type": "Object", + "tags": [], + "label": "executionContext", + "description": [ + "{@link ExecutionContextSetup}" + ], + "signature": [ + "ExecutionContextSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.http", + "type": "CompoundType", + "tags": [], + "label": "http", + "description": [ + "{@link HttpServiceSetup}" + ], + "signature": [ + "HttpServiceSetup", + "<", + "RequestHandlerContext", + "> & { resources: ", + "HttpResources", + "; }" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.i18n", + "type": "Object", + "tags": [], + "label": "i18n", + "description": [ + "{@link I18nServiceSetup}" + ], + "signature": [ + "I18nServiceSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.logging", + "type": "Object", + "tags": [], + "label": "logging", + "description": [ + "{@link LoggingServiceSetup}" + ], + "signature": [ + "LoggingServiceSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.metrics", + "type": "Object", + "tags": [], + "label": "metrics", + "description": [ + "{@link MetricsServiceSetup}" + ], + "signature": [ + "MetricsServiceSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.savedObjects", + "type": "Object", + "tags": [], + "label": "savedObjects", + "description": [ + "{@link SavedObjectsServiceSetup}" + ], + "signature": [ + "SavedObjectsServiceSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.status", + "type": "Object", + "tags": [], + "label": "status", + "description": [ + "{@link StatusServiceSetup}" + ], + "signature": [ + "StatusServiceSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.uiSettings", + "type": "Object", + "tags": [], + "label": "uiSettings", + "description": [ + "{@link UiSettingsServiceSetup}" + ], + "signature": [ + "UiSettingsServiceSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.deprecations", + "type": "Object", + "tags": [], + "label": "deprecations", + "description": [ + "{@link DeprecationsServiceSetup}" + ], + "signature": [ + "DeprecationsServiceSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreSetup.getStartServices", + "type": "Function", + "tags": [], + "label": "getStartServices", + "description": [ + "{@link StartServicesAccessor}" + ], + "signature": [ + "() => Promise<[", + { + "pluginId": "@kbn/core-lifecycle-server", + "scope": "server", + "docId": "kibKbnCoreLifecycleServerPluginApi", + "section": "def-server.CoreStart", + "text": "CoreStart" + }, + ", TPluginsStart, TStart]>" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreStart", + "type": "Interface", + "tags": [], + "label": "CoreStart", + "description": [ + "\nContext passed to the plugins `start` method.\n" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_start.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreStart.analytics", + "type": "Object", + "tags": [], + "label": "analytics", + "description": [ + "{@link AnalyticsServiceStart}" + ], + "signature": [ + "{ optIn: (optInConfig: ", + "OptInConfig", + ") => void; reportEvent: (eventType: string, eventData: EventTypeData) => void; readonly telemetryCounter$: ", + "Observable", + "<", + "TelemetryCounter", + ">; }" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_start.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreStart.capabilities", + "type": "Object", + "tags": [], + "label": "capabilities", + "description": [ + "{@link CapabilitiesStart}" + ], + "signature": [ + "CapabilitiesStart" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_start.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreStart.docLinks", + "type": "Object", + "tags": [], + "label": "docLinks", + "description": [ + "{@link DocLinksServiceStart}" + ], + "signature": [ + "DocLinksServiceSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_start.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreStart.elasticsearch", + "type": "Object", + "tags": [], + "label": "elasticsearch", + "description": [ + "{@link ElasticsearchServiceStart}" + ], + "signature": [ + "ElasticsearchServiceStart" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_start.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreStart.executionContext", + "type": "Object", + "tags": [], + "label": "executionContext", + "description": [ + "{@link ExecutionContextStart}" + ], + "signature": [ + "ExecutionContextSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_start.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreStart.http", + "type": "Object", + "tags": [], + "label": "http", + "description": [ + "{@link HttpServiceStart}" + ], + "signature": [ + "HttpServiceStart" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_start.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreStart.metrics", + "type": "Object", + "tags": [], + "label": "metrics", + "description": [ + "{@link MetricsServiceStart}" + ], + "signature": [ + "MetricsServiceSetup" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_start.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreStart.savedObjects", + "type": "Object", + "tags": [], + "label": "savedObjects", + "description": [ + "{@link SavedObjectsServiceStart}" + ], + "signature": [ + "SavedObjectsServiceStart" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_start.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.CoreStart.uiSettings", + "type": "Object", + "tags": [], + "label": "uiSettings", + "description": [ + "{@link UiSettingsServiceStart}" + ], + "signature": [ + "UiSettingsServiceStart" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_start.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/core-lifecycle-server", + "id": "def-server.StartServicesAccessor", + "type": "Type", + "tags": [], + "label": "StartServicesAccessor", + "description": [ + "\nAllows plugins to get access to APIs available in start inside async handlers.\nPromise will not resolve until Core and plugin dependencies have completed `start`.\nThis should only be used inside handlers registered during `setup` that will only be executed\nafter `start` lifecycle.\n" + ], + "signature": [ + "() => Promise<[", + { + "pluginId": "@kbn/core-lifecycle-server", + "scope": "server", + "docId": "kibKbnCoreLifecycleServerPluginApi", + "section": "def-server.CoreStart", + "text": "CoreStart" + }, + ", TPluginsStart, TStart]>" + ], + "path": "packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [], + "initialIsOpen": false + } + ], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx new file mode 100644 index 0000000000000..fd0edd1ec75b0 --- /dev/null +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -0,0 +1,33 @@ +--- +#### +#### 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: kibKbnCoreLifecycleServerPluginApi +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: 2022-10-27 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] +--- +import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 31 | 0 | 0 | 0 | + +## Server + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_core_lifecycle_server_mocks.devdocs.json b/api_docs/kbn_core_lifecycle_server_mocks.devdocs.json new file mode 100644 index 0000000000000..55aa54ec4fffa --- /dev/null +++ b/api_docs/kbn_core_lifecycle_server_mocks.devdocs.json @@ -0,0 +1,245 @@ +{ + "id": "@kbn/core-lifecycle-server-mocks", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [ + { + "parentPluginId": "@kbn/core-lifecycle-server-mocks", + "id": "def-server.coreInternalLifecycleMock", + "type": "Object", + "tags": [], + "label": "coreInternalLifecycleMock", + "description": [], + "path": "packages/core/lifecycle/core-lifecycle-server-mocks/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-lifecycle-server-mocks", + "id": "def-server.coreInternalLifecycleMock.createInternalPreboot", + "type": "Function", + "tags": [], + "label": "createInternalPreboot", + "description": [], + "signature": [ + "() => { analytics: jest.Mocked<", + "AnalyticsServicePreboot", + ">; context: jest.Mocked<", + "InternalContextSetup", + ">; elasticsearch: ", + "MockedElasticSearchServicePreboot", + "; http: ", + "InternalHttpServicePrebootMock", + "; httpResources: { createRegistrar: jest.Mock, []>; }; uiSettings: jest.Mocked<", + "InternalUiSettingsServicePreboot", + ">; logging: jest.Mocked<", + "InternalLoggingServicePreboot", + ">; preboot: ", + "InternalPrebootServicePrebootMock", + "; }" + ], + "path": "packages/core/lifecycle/core-lifecycle-server-mocks/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-lifecycle-server-mocks", + "id": "def-server.coreInternalLifecycleMock.createInternalSetup", + "type": "Function", + "tags": [], + "label": "createInternalSetup", + "description": [], + "signature": [ + "() => { analytics: jest.Mocked<", + "AnalyticsServiceSetup", + ">; capabilities: jest.Mocked<", + "CapabilitiesSetup", + ">; context: jest.Mocked<", + "InternalContextSetup", + ">; docLinks: ", + "DocLinksServiceSetup", + "; elasticsearch: ", + "MockedInternalElasticSearchServiceSetup", + "; http: ", + "InternalHttpServiceSetupMock", + "; savedObjects: jest.Mocked<", + "InternalSavedObjectsServiceSetup", + ">; status: jest.Mocked<", + "InternalStatusServiceSetup", + ">; environment: jest.Mocked<", + "InternalEnvironmentServicePreboot", + ">; i18n: jest.Mocked<", + "I18nServiceSetup", + ">; httpResources: { createRegistrar: jest.Mock, []>; }; rendering: jest.Mocked<", + "InternalRenderingServiceSetup", + ">; uiSettings: jest.Mocked<", + "UiSettingsServiceSetup", + ">; logging: jest.Mocked<", + "InternalLoggingServicePreboot", + ">; metrics: jest.Mocked<", + "MetricsServiceSetup", + ">; deprecations: jest.Mocked<", + "DeprecationRegistryProvider", + ">; executionContext: jest.Mocked<", + "IExecutionContext", + ">; coreUsageData: jest.Mocked<", + "InternalCoreUsageDataSetup", + ">; }" + ], + "path": "packages/core/lifecycle/core-lifecycle-server-mocks/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-lifecycle-server-mocks", + "id": "def-server.coreInternalLifecycleMock.createInternalStart", + "type": "Function", + "tags": [], + "label": "createInternalStart", + "description": [], + "signature": [ + "() => { analytics: jest.Mocked<", + "AnalyticsServiceStart", + ">; capabilities: jest.Mocked<", + "CapabilitiesStart", + ">; docLinks: ", + "DocLinksServiceSetup", + "; elasticsearch: ", + "MockedElasticSearchServiceStart", + "; http: ", + "InternalHttpServiceStartMock", + "; metrics: jest.Mocked<", + "MetricsServiceSetup", + ">; savedObjects: jest.Mocked<", + "SavedObjectsServiceStart", + ">; uiSettings: jest.Mocked<", + "UiSettingsServiceStart", + ">; coreUsageData: jest.Mocked<", + "CoreUsageDataStart", + ">; executionContext: jest.Mocked<", + "IExecutionContext", + ">; deprecations: jest.Mocked<", + "InternalDeprecationsServiceStart", + ">; }" + ], + "path": "packages/core/lifecycle/core-lifecycle-server-mocks/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-lifecycle-server-mocks", + "id": "def-server.coreLifecycleMock", + "type": "Object", + "tags": [], + "label": "coreLifecycleMock", + "description": [], + "path": "packages/core/lifecycle/core-lifecycle-server-mocks/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-lifecycle-server-mocks", + "id": "def-server.coreLifecycleMock.createPreboot", + "type": "Function", + "tags": [], + "label": "createPreboot", + "description": [], + "signature": [ + "() => CorePrebootMockType" + ], + "path": "packages/core/lifecycle/core-lifecycle-server-mocks/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-lifecycle-server-mocks", + "id": "def-server.coreLifecycleMock.createCoreSetup", + "type": "Function", + "tags": [], + "label": "createCoreSetup", + "description": [], + "signature": [ + "({ pluginStartDeps, pluginStartContract, }?: { pluginStartDeps?: object | undefined; pluginStartContract?: any; }) => CoreSetupMockType" + ], + "path": "packages/core/lifecycle/core-lifecycle-server-mocks/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/core-lifecycle-server-mocks", + "id": "def-server.coreLifecycleMock.createCoreSetup.$1", + "type": "Object", + "tags": [], + "label": "__0", + "description": [], + "signature": [ + "{ pluginStartDeps?: object | undefined; pluginStartContract?: any; }" + ], + "path": "packages/core/lifecycle/core-lifecycle-server-mocks/src/core_setup.mock.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/core-lifecycle-server-mocks", + "id": "def-server.coreLifecycleMock.createCoreStart", + "type": "Function", + "tags": [], + "label": "createCoreStart", + "description": [], + "signature": [ + "() => ", + "MockedKeys", + "<", + "CoreStart", + ">" + ], + "path": "packages/core/lifecycle/core-lifecycle-server-mocks/src/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + } + ], + "initialIsOpen": false + } + ] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx new file mode 100644 index 0000000000000..900c197c3ec01 --- /dev/null +++ b/api_docs/kbn_core_lifecycle_server_mocks.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: kibKbnCoreLifecycleServerMocksPluginApi +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: 2022-10-27 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] +--- +import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 9 | 0 | 9 | 0 | + +## Server + +### Objects + + diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 4d32b8d26576e..6113701710e37 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: 2022-10-20 +date: 2022-10-27 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 ef3e44515d46a..6a6184f69b789 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: 2022-10-20 +date: 2022-10-27 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 f45b8f3fe9991..0ab41b0803443 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: 2022-10-20 +date: 2022-10-27 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 4c6ee9a54a3f4..dc2b8a08bf6a9 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: 2022-10-20 +date: 2022-10-27 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 7596c452104c8..2d81f1ac5aeb2 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: 2022-10-20 +date: 2022-10-27 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 33bffcdebd5b1..75f7ec72d810e 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: 2022-10-20 +date: 2022-10-27 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 1c67f82360251..797638463f27b 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: 2022-10-20 +date: 2022-10-27 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 6c7d84c244062..5f4bd7ea60f1e 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: 2022-10-20 +date: 2022-10-27 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 ef6d579dbbc6a..df099fdfa60b4 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: 2022-10-20 +date: 2022-10-27 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 32ac9adb4d7fa..d898f6f36cb11 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: 2022-10-20 +date: 2022-10-27 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 31d44b6368d02..6e2321a2ec310 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: 2022-10-20 +date: 2022-10-27 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 eb0b48f3258c9..6915d36b4b06b 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: 2022-10-20 +date: 2022-10-27 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 078ae3c322887..fbc89615448c5 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: 2022-10-20 +date: 2022-10-27 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 a62973a308372..0f47d81d3447a 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: 2022-10-20 +date: 2022-10-27 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 615d80e3f3d1d..a138b9c01165a 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: 2022-10-20 +date: 2022-10-27 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 ac228aa11d27f..6607dfce71804 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: 2022-10-20 +date: 2022-10-27 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 245a8de84e0a8..db832acebacae 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: 2022-10-20 +date: 2022-10-27 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 9fd437ea3d524..13e244e8ce7ee 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: 2022-10-20 +date: 2022-10-27 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 adf7e2dff2462..1cd413ad66299 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: 2022-10-20 +date: 2022-10-27 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 8a301dc079228..f26fced74942e 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: 2022-10-20 +date: 2022-10-27 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_server.devdocs.json b/api_docs/kbn_core_plugins_server.devdocs.json new file mode 100644 index 0000000000000..63fcf8fc2dbbc --- /dev/null +++ b/api_docs/kbn_core_plugins_server.devdocs.json @@ -0,0 +1,1097 @@ +{ + "id": "@kbn/core-plugins-server", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.AsyncPlugin", + "type": "Interface", + "tags": [ + "deprecated" + ], + "label": "AsyncPlugin", + "description": [ + "\nA plugin with asynchronous lifecycle methods.\n" + ], + "signature": [ + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.AsyncPlugin", + "text": "AsyncPlugin" + }, + "" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": true, + "removeBy": "8.8.0", + "trackAdoption": false, + "references": [], + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.AsyncPlugin.setup", + "type": "Function", + "tags": [], + "label": "setup", + "description": [], + "signature": [ + "(core: ", + "CoreSetup", + ", plugins: TPluginsSetup) => TSetup | Promise" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.AsyncPlugin.setup.$1", + "type": "Object", + "tags": [], + "label": "core", + "description": [], + "signature": [ + "CoreSetup", + "" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.AsyncPlugin.setup.$2", + "type": "Uncategorized", + "tags": [], + "label": "plugins", + "description": [], + "signature": [ + "TPluginsSetup" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.AsyncPlugin.start", + "type": "Function", + "tags": [], + "label": "start", + "description": [], + "signature": [ + "(core: ", + "CoreStart", + ", plugins: TPluginsStart) => TStart | Promise" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.AsyncPlugin.start.$1", + "type": "Object", + "tags": [], + "label": "core", + "description": [], + "signature": [ + "CoreStart" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.AsyncPlugin.start.$2", + "type": "Uncategorized", + "tags": [], + "label": "plugins", + "description": [], + "signature": [ + "TPluginsStart" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.AsyncPlugin.stop", + "type": "Function", + "tags": [], + "label": "stop", + "description": [], + "signature": [ + "(() => void) | undefined" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.Plugin", + "type": "Interface", + "tags": [], + "label": "Plugin", + "description": [ + "\nThe interface that should be returned by a `PluginInitializer` for a `standard` plugin.\n" + ], + "signature": [ + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.Plugin", + "text": "Plugin" + }, + "" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.Plugin.setup", + "type": "Function", + "tags": [], + "label": "setup", + "description": [], + "signature": [ + "(core: ", + "CoreSetup", + ", plugins: TPluginsSetup) => TSetup" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.Plugin.setup.$1", + "type": "Object", + "tags": [], + "label": "core", + "description": [], + "signature": [ + "CoreSetup", + "" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.Plugin.setup.$2", + "type": "Uncategorized", + "tags": [], + "label": "plugins", + "description": [], + "signature": [ + "TPluginsSetup" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.Plugin.start", + "type": "Function", + "tags": [], + "label": "start", + "description": [], + "signature": [ + "(core: ", + "CoreStart", + ", plugins: TPluginsStart) => TStart" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.Plugin.start.$1", + "type": "Object", + "tags": [], + "label": "core", + "description": [], + "signature": [ + "CoreStart" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.Plugin.start.$2", + "type": "Uncategorized", + "tags": [], + "label": "plugins", + "description": [], + "signature": [ + "TPluginsStart" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.Plugin.stop", + "type": "Function", + "tags": [], + "label": "stop", + "description": [], + "signature": [ + "(() => void) | undefined" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginConfigDescriptor", + "type": "Interface", + "tags": [], + "label": "PluginConfigDescriptor", + "description": [ + "\nDescribes a plugin configuration properties.\n" + ], + "signature": [ + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.PluginConfigDescriptor", + "text": "PluginConfigDescriptor" + }, + "" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginConfigDescriptor.deprecations", + "type": "Function", + "tags": [], + "label": "deprecations", + "description": [ + "\nProvider for the {@link ConfigDeprecation} to apply to the plugin configuration." + ], + "signature": [ + "ConfigDeprecationProvider", + " | undefined" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginConfigDescriptor.exposeToBrowser", + "type": "Object", + "tags": [], + "label": "exposeToBrowser", + "description": [ + "\nList of configuration properties that will be available on the client-side plugin." + ], + "signature": [ + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.ExposedToBrowserDescriptor", + "text": "ExposedToBrowserDescriptor" + }, + " | undefined" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginConfigDescriptor.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [ + "\nSchema to use to validate the plugin configuration.\n\n{@link PluginConfigSchema}" + ], + "signature": [ + "Type", + "" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginConfigDescriptor.exposeToUsage", + "type": "Object", + "tags": [], + "label": "exposeToUsage", + "description": [ + "\nExpose non-default configs to usage collection to be sent via telemetry.\nset a config to `true` to report the actual changed config value.\nset a config to `false` to report the changed config value as [redacted].\n\nAll changed configs except booleans and numbers will be reported\nas [redacted] unless otherwise specified.\n\n{@link MakeUsageFromSchema}" + ], + "signature": [ + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.MakeUsageFromSchema", + "text": "MakeUsageFromSchema" + }, + " | undefined" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginInitializerContext", + "type": "Interface", + "tags": [], + "label": "PluginInitializerContext", + "description": [ + "\nContext that's available to plugins during initialization stage.\n" + ], + "signature": [ + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.PluginInitializerContext", + "text": "PluginInitializerContext" + }, + "" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginInitializerContext.opaqueId", + "type": "Uncategorized", + "tags": [], + "label": "opaqueId", + "description": [], + "signature": [ + "symbol" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginInitializerContext.env", + "type": "Object", + "tags": [], + "label": "env", + "description": [], + "signature": [ + "{ mode: ", + "EnvironmentMode", + "; packageInfo: Readonly<", + "PackageInfo", + ">; instanceUuid: string; configs: readonly string[]; }" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginInitializerContext.node", + "type": "Object", + "tags": [], + "label": "node", + "description": [ + "\nAccess the configuration for this particular Kibana node.\nCan be used to determine which `roles` the current process was started with.\n" + ], + "signature": [ + "NodeInfo" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginInitializerContext.logger", + "type": "Object", + "tags": [], + "label": "logger", + "description": [ + "\n{@link LoggerFactory | logger factory} instance already bound to the plugin's logging context\n" + ], + "signature": [ + "LoggerFactory" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginInitializerContext.config", + "type": "Object", + "tags": [], + "label": "config", + "description": [ + "\nAccessors for the plugin's configuration" + ], + "signature": [ + "{ legacy: { globalConfig$: ", + "Observable", + " moment.Duration; humanize: { (argWithSuffix?: boolean | undefined, argThresholds?: moment.argThresholdOpts | undefined): string; (argThresholds?: moment.argThresholdOpts | undefined): string; }; abs: () => moment.Duration; as: (units: moment.unitOfTime.Base) => number; get: (units: moment.unitOfTime.Base) => number; milliseconds: () => number; asMilliseconds: () => number; seconds: () => number; asSeconds: () => number; minutes: () => number; asMinutes: () => number; hours: () => number; asHours: () => number; days: () => number; asDays: () => number; weeks: () => number; asWeeks: () => number; months: () => number; asMonths: () => number; years: () => number; asYears: () => number; add: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; subtract: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; locale: { (): string; (locale: moment.LocaleSpecifier): moment.Duration; }; localeData: () => moment.Locale; toISOString: () => string; toJSON: () => string; isValid: () => boolean; lang: { (locale: moment.LocaleSpecifier): moment.Moment; (): moment.Locale; }; toIsoString: () => string; }>; readonly shardTimeout: Readonly<{ clone: () => moment.Duration; humanize: { (argWithSuffix?: boolean | undefined, argThresholds?: moment.argThresholdOpts | undefined): string; (argThresholds?: moment.argThresholdOpts | undefined): string; }; abs: () => moment.Duration; as: (units: moment.unitOfTime.Base) => number; get: (units: moment.unitOfTime.Base) => number; milliseconds: () => number; asMilliseconds: () => number; seconds: () => number; asSeconds: () => number; minutes: () => number; asMinutes: () => number; hours: () => number; asHours: () => number; days: () => number; asDays: () => number; weeks: () => number; asWeeks: () => number; months: () => number; asMonths: () => number; years: () => number; asYears: () => number; add: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; subtract: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; locale: { (): string; (locale: moment.LocaleSpecifier): moment.Duration; }; localeData: () => moment.Locale; toISOString: () => string; toJSON: () => string; isValid: () => boolean; lang: { (locale: moment.LocaleSpecifier): moment.Moment; (): moment.Locale; }; toIsoString: () => string; }>; readonly pingTimeout: Readonly<{ clone: () => moment.Duration; humanize: { (argWithSuffix?: boolean | undefined, argThresholds?: moment.argThresholdOpts | undefined): string; (argThresholds?: moment.argThresholdOpts | undefined): string; }; abs: () => moment.Duration; as: (units: moment.unitOfTime.Base) => number; get: (units: moment.unitOfTime.Base) => number; milliseconds: () => number; asMilliseconds: () => number; seconds: () => number; asSeconds: () => number; minutes: () => number; asMinutes: () => number; hours: () => number; asHours: () => number; days: () => number; asDays: () => number; weeks: () => number; asWeeks: () => number; months: () => number; asMonths: () => number; years: () => number; asYears: () => number; add: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; subtract: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; locale: { (): string; (locale: moment.LocaleSpecifier): moment.Duration; }; localeData: () => moment.Locale; toISOString: () => string; toJSON: () => string; isValid: () => boolean; lang: { (locale: moment.LocaleSpecifier): moment.Moment; (): moment.Locale; }; toIsoString: () => string; }>; }>; path: Readonly<{ readonly data: string; }>; savedObjects: Readonly<{ readonly maxImportPayloadBytes: Readonly<{ isGreaterThan: (other: ", + "ByteSizeValue", + ") => boolean; isLessThan: (other: ", + "ByteSizeValue", + ") => boolean; isEqualTo: (other: ", + "ByteSizeValue", + ") => boolean; getValueInBytes: () => number; toString: (returnUnit?: ByteSizeValueUnit | undefined) => string; }>; }>; }>>; get: () => Readonly<{ elasticsearch: Readonly<{ readonly requestTimeout: Readonly<{ clone: () => moment.Duration; humanize: { (argWithSuffix?: boolean | undefined, argThresholds?: moment.argThresholdOpts | undefined): string; (argThresholds?: moment.argThresholdOpts | undefined): string; }; abs: () => moment.Duration; as: (units: moment.unitOfTime.Base) => number; get: (units: moment.unitOfTime.Base) => number; milliseconds: () => number; asMilliseconds: () => number; seconds: () => number; asSeconds: () => number; minutes: () => number; asMinutes: () => number; hours: () => number; asHours: () => number; days: () => number; asDays: () => number; weeks: () => number; asWeeks: () => number; months: () => number; asMonths: () => number; years: () => number; asYears: () => number; add: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; subtract: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; locale: { (): string; (locale: moment.LocaleSpecifier): moment.Duration; }; localeData: () => moment.Locale; toISOString: () => string; toJSON: () => string; isValid: () => boolean; lang: { (locale: moment.LocaleSpecifier): moment.Moment; (): moment.Locale; }; toIsoString: () => string; }>; readonly shardTimeout: Readonly<{ clone: () => moment.Duration; humanize: { (argWithSuffix?: boolean | undefined, argThresholds?: moment.argThresholdOpts | undefined): string; (argThresholds?: moment.argThresholdOpts | undefined): string; }; abs: () => moment.Duration; as: (units: moment.unitOfTime.Base) => number; get: (units: moment.unitOfTime.Base) => number; milliseconds: () => number; asMilliseconds: () => number; seconds: () => number; asSeconds: () => number; minutes: () => number; asMinutes: () => number; hours: () => number; asHours: () => number; days: () => number; asDays: () => number; weeks: () => number; asWeeks: () => number; months: () => number; asMonths: () => number; years: () => number; asYears: () => number; add: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; subtract: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; locale: { (): string; (locale: moment.LocaleSpecifier): moment.Duration; }; localeData: () => moment.Locale; toISOString: () => string; toJSON: () => string; isValid: () => boolean; lang: { (locale: moment.LocaleSpecifier): moment.Moment; (): moment.Locale; }; toIsoString: () => string; }>; readonly pingTimeout: Readonly<{ clone: () => moment.Duration; humanize: { (argWithSuffix?: boolean | undefined, argThresholds?: moment.argThresholdOpts | undefined): string; (argThresholds?: moment.argThresholdOpts | undefined): string; }; abs: () => moment.Duration; as: (units: moment.unitOfTime.Base) => number; get: (units: moment.unitOfTime.Base) => number; milliseconds: () => number; asMilliseconds: () => number; seconds: () => number; asSeconds: () => number; minutes: () => number; asMinutes: () => number; hours: () => number; asHours: () => number; days: () => number; asDays: () => number; weeks: () => number; asWeeks: () => number; months: () => number; asMonths: () => number; years: () => number; asYears: () => number; add: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; subtract: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; locale: { (): string; (locale: moment.LocaleSpecifier): moment.Duration; }; localeData: () => moment.Locale; toISOString: () => string; toJSON: () => string; isValid: () => boolean; lang: { (locale: moment.LocaleSpecifier): moment.Moment; (): moment.Locale; }; toIsoString: () => string; }>; }>; path: Readonly<{ readonly data: string; }>; savedObjects: Readonly<{ readonly maxImportPayloadBytes: Readonly<{ isGreaterThan: (other: ", + "ByteSizeValue", + ") => boolean; isLessThan: (other: ", + "ByteSizeValue", + ") => boolean; isEqualTo: (other: ", + "ByteSizeValue", + ") => boolean; getValueInBytes: () => number; toString: (returnUnit?: ByteSizeValueUnit | undefined) => string; }>; }>; }>; }; create: () => ", + "Observable", + "; get: () => T; }" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest", + "type": "Interface", + "tags": [], + "label": "PluginManifest", + "description": [ + "\nDescribes the set of required and optional properties plugin can define in its\nmandatory JSON manifest file.\n" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.id", + "type": "string", + "tags": [], + "label": "id", + "description": [ + "\nIdentifier of the plugin. Must be a string in camelCase. Part of a plugin public contract.\nOther plugins leverage it to access plugin API, navigate to the plugin, etc." + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.version", + "type": "string", + "tags": [], + "label": "version", + "description": [ + "\nVersion of the plugin." + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.kibanaVersion", + "type": "string", + "tags": [], + "label": "kibanaVersion", + "description": [ + "\nThe version of Kibana the plugin is compatible with, defaults to \"version\"." + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.type", + "type": "Enum", + "tags": [], + "label": "type", + "description": [ + "\nType of the plugin, defaults to `standard`." + ], + "signature": [ + "PluginType" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.configPath", + "type": "CompoundType", + "tags": [], + "label": "configPath", + "description": [ + "\nRoot {@link ConfigPath | configuration path} used by the plugin, defaults\nto \"id\" in snake_case format.\n" + ], + "signature": [ + "string | string[]" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.requiredPlugins", + "type": "Object", + "tags": [], + "label": "requiredPlugins", + "description": [ + "\nAn optional list of the other plugins that **must be** installed and enabled\nfor this plugin to function properly." + ], + "signature": [ + "readonly string[]" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.requiredBundles", + "type": "Object", + "tags": [], + "label": "requiredBundles", + "description": [ + "\nList of plugin ids that this plugin's UI code imports modules from that are\nnot in `requiredPlugins`.\n" + ], + "signature": [ + "readonly string[]" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.optionalPlugins", + "type": "Object", + "tags": [], + "label": "optionalPlugins", + "description": [ + "\nAn optional list of the other plugins that if installed and enabled **may be**\nleveraged by this plugin for some additional functionality but otherwise are\nnot required for this plugin to work properly." + ], + "signature": [ + "readonly string[]" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.ui", + "type": "boolean", + "tags": [], + "label": "ui", + "description": [ + "\nSpecifies whether plugin includes some client/browser specific functionality\nthat should be included into client bundle via `public/ui_plugin.js` file." + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.server", + "type": "boolean", + "tags": [], + "label": "server", + "description": [ + "\nSpecifies whether plugin includes some server-side specific functionality." + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.extraPublicDirs", + "type": "Array", + "tags": [ + "deprecated" + ], + "label": "extraPublicDirs", + "description": [ + "\nSpecifies directory names that can be imported by other ui-plugins built\nusing the same instance of the @kbn/optimizer. A temporary measure we plan\nto replace with better mechanisms for sharing static code between plugins" + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": true, + "trackAdoption": false, + "references": [] + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.serviceFolders", + "type": "Object", + "tags": [], + "label": "serviceFolders", + "description": [ + "\nOnly used for the automatically generated API documentation. Specifying service\nfolders will cause your plugin API reference to be broken up into sub sections." + ], + "signature": [ + "readonly string[] | undefined" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.owner", + "type": "Object", + "tags": [], + "label": "owner", + "description": [], + "signature": [ + "{ readonly name: string; readonly githubTeam?: string | undefined; }" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.description", + "type": "string", + "tags": [], + "label": "description", + "description": [ + "\nTODO: make required once all plugins specify this.\nA brief description of what this plugin does and any capabilities it provides." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginManifest.enabledOnAnonymousPages", + "type": "CompoundType", + "tags": [], + "label": "enabledOnAnonymousPages", + "description": [ + "\nSpecifies whether this plugin - and its required dependencies - will be enabled for anonymous pages (login page, status page when\nconfigured, etc.) Default is false." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PrebootPlugin", + "type": "Interface", + "tags": [], + "label": "PrebootPlugin", + "description": [ + "\nThe interface that should be returned by a `PluginInitializer` for a `preboot` plugin.\n" + ], + "signature": [ + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.PrebootPlugin", + "text": "PrebootPlugin" + }, + "" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PrebootPlugin.setup", + "type": "Function", + "tags": [], + "label": "setup", + "description": [], + "signature": [ + "(core: ", + "CorePreboot", + ", plugins: TPluginsSetup) => TSetup" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PrebootPlugin.setup.$1", + "type": "Object", + "tags": [], + "label": "core", + "description": [], + "signature": [ + "CorePreboot" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PrebootPlugin.setup.$2", + "type": "Uncategorized", + "tags": [], + "label": "plugins", + "description": [], + "signature": [ + "TPluginsSetup" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PrebootPlugin.stop", + "type": "Function", + "tags": [], + "label": "stop", + "description": [], + "signature": [ + "(() => void) | undefined" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.ExposedToBrowserDescriptor", + "type": "Type", + "tags": [], + "label": "ExposedToBrowserDescriptor", + "description": [ + "\nType defining the list of configuration properties that will be exposed on the client-side\nObject properties can either be fully exposed\n" + ], + "signature": [ + "{ [Key in keyof T]?: (T[Key] extends Maybe ? boolean : T[Key] extends Maybe ? boolean | ", + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.ExposedToBrowserDescriptor", + "text": "ExposedToBrowserDescriptor" + }, + " : boolean) | undefined; }" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.MakeUsageFromSchema", + "type": "Type", + "tags": [], + "label": "MakeUsageFromSchema", + "description": [ + "\nList of configuration values that will be exposed to usage collection.\nIf parent node or actual config path is set to `true` then the actual value\nof these configs will be reoprted.\nIf parent node or actual config path is set to `false` then the config\nwill be reported as [redacted].\n" + ], + "signature": [ + "{ [Key in keyof T]?: (T[Key] extends Maybe ? false : T[Key] extends Maybe ? boolean : T[Key] extends Maybe ? boolean | ", + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.MakeUsageFromSchema", + "text": "MakeUsageFromSchema" + }, + " : boolean) | undefined; }" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginConfigSchema", + "type": "Type", + "tags": [], + "label": "PluginConfigSchema", + "description": [ + "\nDedicated type for plugin configuration schema.\n" + ], + "signature": [ + "Type", + "" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginInitializer", + "type": "Type", + "tags": [], + "label": "PluginInitializer", + "description": [ + "\nThe `plugin` export at the root of a plugin's `server` directory should conform\nto this interface.\n" + ], + "signature": [ + "(core: ", + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.PluginInitializerContext", + "text": "PluginInitializerContext" + }, + ") => ", + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.Plugin", + "text": "Plugin" + }, + " | ", + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.PrebootPlugin", + "text": "PrebootPlugin" + }, + " | ", + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.AsyncPlugin", + "text": "AsyncPlugin" + }, + "" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.PluginInitializer.$1", + "type": "Object", + "tags": [], + "label": "core", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-plugins-server", + "scope": "server", + "docId": "kibKbnCorePluginsServerPluginApi", + "section": "def-server.PluginInitializerContext", + "text": "PluginInitializerContext" + }, + "" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.SharedGlobalConfig", + "type": "Type", + "tags": [], + "label": "SharedGlobalConfig", + "description": [], + "signature": [ + "{ readonly elasticsearch: Readonly<{ readonly requestTimeout: Readonly<{ clone: () => moment.Duration; humanize: { (argWithSuffix?: boolean | undefined, argThresholds?: moment.argThresholdOpts | undefined): string; (argThresholds?: moment.argThresholdOpts | undefined): string; }; abs: () => moment.Duration; as: (units: moment.unitOfTime.Base) => number; get: (units: moment.unitOfTime.Base) => number; milliseconds: () => number; asMilliseconds: () => number; seconds: () => number; asSeconds: () => number; minutes: () => number; asMinutes: () => number; hours: () => number; asHours: () => number; days: () => number; asDays: () => number; weeks: () => number; asWeeks: () => number; months: () => number; asMonths: () => number; years: () => number; asYears: () => number; add: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; subtract: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; locale: { (): string; (locale: moment.LocaleSpecifier): moment.Duration; }; localeData: () => moment.Locale; toISOString: () => string; toJSON: () => string; isValid: () => boolean; lang: { (locale: moment.LocaleSpecifier): moment.Moment; (): moment.Locale; }; toIsoString: () => string; }>; readonly shardTimeout: Readonly<{ clone: () => moment.Duration; humanize: { (argWithSuffix?: boolean | undefined, argThresholds?: moment.argThresholdOpts | undefined): string; (argThresholds?: moment.argThresholdOpts | undefined): string; }; abs: () => moment.Duration; as: (units: moment.unitOfTime.Base) => number; get: (units: moment.unitOfTime.Base) => number; milliseconds: () => number; asMilliseconds: () => number; seconds: () => number; asSeconds: () => number; minutes: () => number; asMinutes: () => number; hours: () => number; asHours: () => number; days: () => number; asDays: () => number; weeks: () => number; asWeeks: () => number; months: () => number; asMonths: () => number; years: () => number; asYears: () => number; add: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; subtract: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; locale: { (): string; (locale: moment.LocaleSpecifier): moment.Duration; }; localeData: () => moment.Locale; toISOString: () => string; toJSON: () => string; isValid: () => boolean; lang: { (locale: moment.LocaleSpecifier): moment.Moment; (): moment.Locale; }; toIsoString: () => string; }>; readonly pingTimeout: Readonly<{ clone: () => moment.Duration; humanize: { (argWithSuffix?: boolean | undefined, argThresholds?: moment.argThresholdOpts | undefined): string; (argThresholds?: moment.argThresholdOpts | undefined): string; }; abs: () => moment.Duration; as: (units: moment.unitOfTime.Base) => number; get: (units: moment.unitOfTime.Base) => number; milliseconds: () => number; asMilliseconds: () => number; seconds: () => number; asSeconds: () => number; minutes: () => number; asMinutes: () => number; hours: () => number; asHours: () => number; days: () => number; asDays: () => number; weeks: () => number; asWeeks: () => number; months: () => number; asMonths: () => number; years: () => number; asYears: () => number; add: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; subtract: (inp?: moment.DurationInputArg1, unit?: moment.unitOfTime.DurationConstructor | undefined) => moment.Duration; locale: { (): string; (locale: moment.LocaleSpecifier): moment.Duration; }; localeData: () => moment.Locale; toISOString: () => string; toJSON: () => string; isValid: () => boolean; lang: { (locale: moment.LocaleSpecifier): moment.Moment; (): moment.Locale; }; toIsoString: () => string; }>; }>; readonly path: Readonly<{ readonly data: string; }>; readonly savedObjects: Readonly<{ readonly maxImportPayloadBytes: Readonly<{ isGreaterThan: (other: ", + "ByteSizeValue", + ") => boolean; isLessThan: (other: ", + "ByteSizeValue", + ") => boolean; isEqualTo: (other: ", + "ByteSizeValue", + ") => boolean; getValueInBytes: () => number; toString: (returnUnit?: ByteSizeValueUnit | undefined) => string; }>; }>; }" + ], + "path": "packages/core/plugins/core-plugins-server/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.SharedGlobalConfigKeys", + "type": "Object", + "tags": [], + "label": "SharedGlobalConfigKeys", + "description": [], + "path": "packages/core/plugins/core-plugins-server/src/shared_global_config.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.SharedGlobalConfigKeys.elasticsearch", + "type": "Object", + "tags": [], + "label": "elasticsearch", + "description": [ + "// We can add more if really needed" + ], + "signature": [ + "readonly [\"shardTimeout\", \"requestTimeout\", \"pingTimeout\"]" + ], + "path": "packages/core/plugins/core-plugins-server/src/shared_global_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.SharedGlobalConfigKeys.path", + "type": "Object", + "tags": [], + "label": "path", + "description": [], + "signature": [ + "readonly [\"data\"]" + ], + "path": "packages/core/plugins/core-plugins-server/src/shared_global_config.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-plugins-server", + "id": "def-server.SharedGlobalConfigKeys.savedObjects", + "type": "Object", + "tags": [], + "label": "savedObjects", + "description": [], + "signature": [ + "readonly [\"maxImportPayloadBytes\"]" + ], + "path": "packages/core/plugins/core-plugins-server/src/shared_global_config.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx new file mode 100644 index 0000000000000..51f576d18fed4 --- /dev/null +++ b/api_docs/kbn_core_plugins_server.mdx @@ -0,0 +1,36 @@ +--- +#### +#### 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: kibKbnCorePluginsServerPluginApi +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: 2022-10-27 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] +--- +import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 58 | 0 | 26 | 0 | + +## Server + +### Objects + + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_core_plugins_server_mocks.devdocs.json b/api_docs/kbn_core_plugins_server_mocks.devdocs.json new file mode 100644 index 0000000000000..e7e11c0504caa --- /dev/null +++ b/api_docs/kbn_core_plugins_server_mocks.devdocs.json @@ -0,0 +1,107 @@ +{ + "id": "@kbn/core-plugins-server-mocks", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [ + { + "parentPluginId": "@kbn/core-plugins-server-mocks", + "id": "def-server.pluginServiceMock", + "type": "Object", + "tags": [], + "label": "pluginServiceMock", + "description": [], + "path": "packages/core/plugins/core-plugins-server-mocks/src/plugins_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-plugins-server-mocks", + "id": "def-server.pluginServiceMock.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [], + "signature": [ + "() => PluginsServiceMock" + ], + "path": "packages/core/plugins/core-plugins-server-mocks/src/plugins_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-plugins-server-mocks", + "id": "def-server.pluginServiceMock.createSetupContract", + "type": "Function", + "tags": [], + "label": "createSetupContract", + "description": [], + "signature": [ + "() => ", + "PluginsServiceSetup" + ], + "path": "packages/core/plugins/core-plugins-server-mocks/src/plugins_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-plugins-server-mocks", + "id": "def-server.pluginServiceMock.createStartContract", + "type": "Function", + "tags": [], + "label": "createStartContract", + "description": [], + "signature": [ + "() => { contracts: Map; }" + ], + "path": "packages/core/plugins/core-plugins-server-mocks/src/plugins_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-plugins-server-mocks", + "id": "def-server.pluginServiceMock.createUiPlugins", + "type": "Function", + "tags": [], + "label": "createUiPlugins", + "description": [], + "signature": [ + "() => { browserConfigs: Map; internal: Map; public: Map; }" + ], + "path": "packages/core/plugins/core-plugins-server-mocks/src/plugins_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + } + ], + "initialIsOpen": false + } + ] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx new file mode 100644 index 0000000000000..84795962d95f4 --- /dev/null +++ b/api_docs/kbn_core_plugins_server_mocks.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: kibKbnCorePluginsServerMocksPluginApi +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: 2022-10-27 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] +--- +import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 5 | 0 | 5 | 0 | + +## Server + +### Objects + + diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 4b05cd683fff0..7e2bd637ba5e6 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: 2022-10-20 +date: 2022-10-27 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 77b9ab16de808..c13943e7cb4cc 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: 2022-10-20 +date: 2022-10-27 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 9f7454efe9463..c1f2d33838f78 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: 2022-10-20 +date: 2022-10-27 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 4b8b277d3f188..fc3536fa21756 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: 2022-10-20 +date: 2022-10-27 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 722724733b9fb..287636e6d041d 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: 2022-10-20 +date: 2022-10-27 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_saved_objects_api_browser.devdocs.json b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json index 19434ae87b701..06e843f3cd6f5 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -1793,6 +1793,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-saved-objects-api-browser", + "id": "def-common.SimpleSavedObject.createdAt", + "type": "string", + "tags": [], + "label": "createdAt", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-saved-objects-api-browser", "id": "def-common.SimpleSavedObject.namespaces", diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index c77ca067ccf79..07b29d82a08a4 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 106 | 1 | 75 | 0 | +| 107 | 1 | 76 | 0 | ## Common diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index ebbc378e704b5..c63b6735bb2a6 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: 2022-10-20 +date: 2022-10-27 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_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index 997e4865963cd..2dfc962709cb2 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.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 fdad2201eb1d6..9653115a36709 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: 2022-10-20 +date: 2022-10-27 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 0abea07df3d82..fc1451a1120dd 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: 2022-10-20 +date: 2022-10-27 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 c47baa097dee1..5c5c24028aa56 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: 2022-10-20 +date: 2022-10-27 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 34301e6c36c42..0097116279d5b 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: 2022-10-20 +date: 2022-10-27 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 fa0c5b3358ef0..45dd67d7de0fe 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: 2022-10-20 +date: 2022-10-27 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 b5f134fd1f25b..66e2a3498e5b5 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: 2022-10-20 +date: 2022-10-27 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.devdocs.json b/api_docs/kbn_core_saved_objects_common.devdocs.json index 78a36566609fa..4a004f5e967eb 100644 --- a/api_docs/kbn_core_saved_objects_common.devdocs.json +++ b/api_docs/kbn_core_saved_objects_common.devdocs.json @@ -83,6 +83,22 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-saved-objects-common", + "id": "def-common.SavedObject.created_at", + "type": "string", + "tags": [], + "label": "created_at", + "description": [ + "Timestamp of the time this document had been created." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-common/src/saved_objects.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-saved-objects-common", "id": "def-common.SavedObject.updated_at", diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index fd555d015d896..28123d66e72b6 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 82 | 0 | 41 | 0 | +| 83 | 0 | 41 | 0 | ## Common 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 756bea18eecce..4f264eb6ba3dd 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: 2022-10-20 +date: 2022-10-27 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 aa038f44c60fc..35c85a196bda5 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: 2022-10-20 +date: 2022-10-27 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 35a0ccf314ac5..e9054f5ddac0d 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: 2022-10-20 +date: 2022-10-27 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 542316d98f76c..ae39f9274383d 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: 2022-10-20 +date: 2022-10-27 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.devdocs.json b/api_docs/kbn_core_saved_objects_server.devdocs.json index f21f0a648a391..29e8376dafd9b 100644 --- a/api_docs/kbn_core_saved_objects_server.devdocs.json +++ b/api_docs/kbn_core_saved_objects_server.devdocs.json @@ -2193,6 +2193,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-saved-objects-server", + "id": "def-server.SavedObjectsRawDocSource.created_at", + "type": "string", + "tags": [], + "label": "created_at", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/core/saved-objects/core-saved-objects-server/src/serialization.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-saved-objects-server", "id": "def-server.SavedObjectsRawDocSource.references", diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 5bede64640645..ccbd986403a12 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact Kibana Core for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 225 | 0 | 82 | 0 | +| 226 | 0 | 83 | 0 | ## Server diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 7151d4e301670..839a20bb8de36 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: 2022-10-20 +date: 2022-10-27 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 9bad53c1d90f8..6f5d06ad2b941 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: 2022-10-20 +date: 2022-10-27 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 4486bb7a77ff7..bd4852894104d 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: 2022-10-20 +date: 2022-10-27 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_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 499573c017481..946aa201abb92 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: 2022-10-20 +date: 2022-10-27 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 d4d8e4b2f3935..4871aa5dbf836 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: 2022-10-20 +date: 2022-10-27 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 fd36bed8802a8..65c4b49cc470f 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: 2022-10-20 +date: 2022-10-27 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 98a84b8888f48..836cfcbf72d4e 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: 2022-10-20 +date: 2022-10-27 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 b1ba5573c1275..bc3a711c881b2 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: 2022-10-20 +date: 2022-10-27 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 0a908c13b90c9..4c82257440566 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: 2022-10-20 +date: 2022-10-27 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 c8f38f90961c8..d2b9679b27485 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: 2022-10-20 +date: 2022-10-27 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_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index aa136ebc4314b..ea7fcee358535 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: 2022-10-20 +date: 2022-10-27 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 222e433adcc57..33f8554abc430 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: 2022-10-20 +date: 2022-10-27 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 525c3d33a19e2..7d06a917fea76 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: 2022-10-20 +date: 2022-10-27 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_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 8c0b9308f3fe2..c06f92445d910 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index e49eb21b72d26..6915b72b847f0 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: 2022-10-20 +date: 2022-10-27 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 bb3dbc049e157..428f0ff8cba7b 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: 2022-10-20 +date: 2022-10-27 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 775384f6e429a..b2e3b263a1e12 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: 2022-10-20 +date: 2022-10-27 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 114cbc96fd128..24937e248cf85 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: 2022-10-20 +date: 2022-10-27 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 7c52db31d9215..d021de220cde3 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: 2022-10-20 +date: 2022-10-27 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 384a98dc0e815..255a43d04b5f7 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: 2022-10-20 +date: 2022-10-27 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 4977e9d3bd1d8..d8d991b65676f 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: 2022-10-20 +date: 2022-10-27 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 f2a3cfad2cfd7..d5d48e7fab1f6 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: 2022-10-20 +date: 2022-10-27 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 392f8b70696e8..f4cd19d007398 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: 2022-10-20 +date: 2022-10-27 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 1ce520759eda5..0c8f8a0669504 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: 2022-10-20 +date: 2022-10-27 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 64d65497cfa2e..28ff986dadff2 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: 2022-10-20 +date: 2022-10-27 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_crypto.mdx b/api_docs/kbn_crypto.mdx index c0cbc0603ed6c..7ab25ffd5d55b 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: 2022-10-20 +date: 2022-10-27 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 078ff04fd52d0..547cb2dd95c51 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index ecbd50655ac80..978e44f341937 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 171f762efd8aa..1d66e07c0d9ad 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: 2022-10-20 +date: 2022-10-27 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 db58fbd4b64f0..e21e1f9121950 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: 2022-10-20 +date: 2022-10-27 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 74b0500f0da36..4d0e6133ab751 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: 2022-10-20 +date: 2022-10-27 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 8a033fd3cce5a..83a050a930540 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index a12b3ae2f8792..59846f75e673f 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -160,7 +160,7 @@ "label": "apm", "description": [], "signature": [ - "{ readonly kibanaSettings: string; readonly supportedServiceMaps: string; readonly customLinks: string; readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; readonly overview: string; readonly tailSamplingPolicies: string; readonly elasticAgent: string; }" + "{ readonly kibanaSettings: string; readonly supportedServiceMaps: string; readonly customLinks: string; readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; readonly overview: string; readonly tailSamplingPolicies: string; readonly elasticAgent: string; readonly storageExplorer: string; readonly spanCompression: string; readonly transactionSampling: string; readonly indexLifecycleManagement: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 3cb3447738cf8..dc4559557ade6 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: 2022-10-20 +date: 2022-10-27 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 7d49ef654a7fb..347e99141ab8c 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index a3825c1b98894..1b7516969bda4 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index da654357a439d..0ac44d607848e 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: 2022-10-20 +date: 2022-10-27 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 9913165ebf9c9..61123022d414b 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: 2022-10-20 +date: 2022-10-27 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 bfda31d414f0d..c107cd654437a 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: 2022-10-20 +date: 2022-10-27 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 7aa8e89065f97..a542cc1e36085 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: 2022-10-20 +date: 2022-10-27 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 5767e3df777d6..ee8d5c6b0659b 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 70890717529c6..40fbf2a84b17a 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index e1c7c9b64148c..6895e16b968d7 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: 2022-10-20 +date: 2022-10-27 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_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index aaae772f0474d..9978753506a21 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: 2022-10-20 +date: 2022-10-27 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_generate.mdx b/api_docs/kbn_generate.mdx index 03c3adae55946..17c948b9c7077 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index c75d1541b415c..bb6c09725c4cf 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.devdocs.json b/api_docs/kbn_guided_onboarding.devdocs.json new file mode 100644 index 0000000000000..01294a25e7450 --- /dev/null +++ b/api_docs/kbn_guided_onboarding.devdocs.json @@ -0,0 +1,292 @@ +{ + "id": "@kbn/guided-onboarding", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.GuideCard", + "type": "Function", + "tags": [], + "label": "GuideCard", + "description": [], + "signature": [ + "({ useCase, guides, activateGuide, isDarkTheme, addBasePath, }: ", + "GuideCardProps", + ") => JSX.Element" + ], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/guide_card.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.GuideCard.$1", + "type": "Object", + "tags": [], + "label": "{\n useCase,\n guides,\n activateGuide,\n isDarkTheme,\n addBasePath,\n}", + "description": [], + "signature": [ + "GuideCardProps" + ], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/guide_card.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.ObservabilityLinkCard", + "type": "Function", + "tags": [], + "label": "ObservabilityLinkCard", + "description": [], + "signature": [ + "({ navigateToApp, isDarkTheme, addBasePath, }: { navigateToApp: (appId: string, options?: ", + "NavigateToAppOptions", + " | undefined) => Promise; isDarkTheme: boolean; addBasePath: (url: string) => string; }) => JSX.Element" + ], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/observability_link_card.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.ObservabilityLinkCard.$1", + "type": "Object", + "tags": [], + "label": "{\n navigateToApp,\n isDarkTheme,\n addBasePath,\n}", + "description": [], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/observability_link_card.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.ObservabilityLinkCard.$1.navigateToApp", + "type": "Function", + "tags": [], + "label": "navigateToApp", + "description": [], + "signature": [ + "(appId: string, options?: ", + "NavigateToAppOptions", + " | undefined) => Promise" + ], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/observability_link_card.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.ObservabilityLinkCard.$1.navigateToApp.$1", + "type": "string", + "tags": [], + "label": "appId", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/observability_link_card.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.ObservabilityLinkCard.$1.navigateToApp.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "NavigateToAppOptions", + " | undefined" + ], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/observability_link_card.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.ObservabilityLinkCard.$1.isDarkTheme", + "type": "boolean", + "tags": [], + "label": "isDarkTheme", + "description": [], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/observability_link_card.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.ObservabilityLinkCard.$1.addBasePath", + "type": "Function", + "tags": [], + "label": "addBasePath", + "description": [], + "signature": [ + "(url: string) => string" + ], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/observability_link_card.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.ObservabilityLinkCard.$1.addBasePath.$1", + "type": "string", + "tags": [], + "label": "url", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/observability_link_card.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.GuideState", + "type": "Interface", + "tags": [], + "label": "GuideState", + "description": [], + "path": "packages/kbn-guided-onboarding/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.GuideState.guideId", + "type": "CompoundType", + "tags": [], + "label": "guideId", + "description": [], + "signature": [ + "\"search\" | \"security\" | \"observability\"" + ], + "path": "packages/kbn-guided-onboarding/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.GuideState.status", + "type": "CompoundType", + "tags": [], + "label": "status", + "description": [], + "signature": [ + "\"complete\" | \"not_started\" | \"in_progress\" | \"ready_to_complete\"" + ], + "path": "packages/kbn-guided-onboarding/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.GuideState.isActive", + "type": "CompoundType", + "tags": [], + "label": "isActive", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-guided-onboarding/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.GuideState.steps", + "type": "Array", + "tags": [], + "label": "steps", + "description": [], + "signature": [ + "GuideStep", + "[]" + ], + "path": "packages/kbn-guided-onboarding/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.GuideId", + "type": "Type", + "tags": [], + "label": "GuideId", + "description": [], + "signature": [ + "\"search\" | \"security\" | \"observability\"" + ], + "path": "packages/kbn-guided-onboarding/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/guided-onboarding", + "id": "def-common.UseCase", + "type": "Type", + "tags": [], + "label": "UseCase", + "description": [], + "signature": [ + "\"search\" | \"security\" | \"observability\"" + ], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx new file mode 100644 index 0000000000000..7f7fa468a89d6 --- /dev/null +++ b/api_docs/kbn_guided_onboarding.mdx @@ -0,0 +1,36 @@ +--- +#### +#### 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: kibKbnGuidedOnboardingPluginApi +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: 2022-10-27 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] +--- +import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; + + + +Contact [Owner missing] for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 17 | 0 | 17 | 2 | + +## Common + +### Functions + + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index c74dfa715656f..15f2ee2eb0fdd 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: 2022-10-20 +date: 2022-10-27 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 5cd93024fd21a..d8baa5c80d644 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 66717f174d7ca..0a336e791151b 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: 2022-10-20 +date: 2022-10-27 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 059bde302b82d..75a09df57f806 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: 2022-10-20 +date: 2022-10-27 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 fd0e2de299df9..d04acc3a80f60 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 11485cdadb8b9..3e5055b1d0ca3 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 7a77370726a76..1b5413a3cf985 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 365496e0818e4..fe0c11bb61a79 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index ab7866d328889..74d33a8eeedad 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: 2022-10-20 +date: 2022-10-27 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 05168e70d9fec..f638de14134ee 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index ff51909df5133..e697201146686 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: 2022-10-20 +date: 2022-10-27 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_popover.devdocs.json b/api_docs/kbn_language_documentation_popover.devdocs.json index c0faf383d25f6..efc5a267ed84c 100644 --- a/api_docs/kbn_language_documentation_popover.devdocs.json +++ b/api_docs/kbn_language_documentation_popover.devdocs.json @@ -50,6 +50,38 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/language-documentation-popover", + "id": "def-common.LanguageDocumentationPopoverContent", + "type": "Function", + "tags": [], + "label": "LanguageDocumentationPopoverContent", + "description": [], + "signature": [ + "React.NamedExoticComponent & { readonly type: ({ language, sections }: DocumentationProps) => JSX.Element; }" + ], + "path": "packages/kbn-language-documentation-popover/src/components/documentation_content.tsx", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/language-documentation-popover", + "id": "def-common.LanguageDocumentationPopoverContent.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false } ], "interfaces": [ diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index 8b6f96e1ff6db..73e95c810101a 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 5 | 0 | 4 | 0 | +| 7 | 0 | 5 | 0 | ## Common diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index ed9dec2418b85..e2000be3c7763 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: 2022-10-20 +date: 2022-10-27 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 ccee0631ef96e..09c885353218c 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 6efdcc34c59c2..b0bbe3723f285 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index d6d6fa081932a..383e914616ccc 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.devdocs.json b/api_docs/kbn_ml_agg_utils.devdocs.json index 6c021a7a5bc78..efb59fbc5c166 100644 --- a/api_docs/kbn_ml_agg_utils.devdocs.json +++ b/api_docs/kbn_ml_agg_utils.devdocs.json @@ -87,7 +87,7 @@ }, "[], samplerShardSize: number, runtimeMappings?: ", "MappingRuntimeFields", - " | undefined) => Promise<", + " | undefined, abortSignal?: AbortSignal | undefined) => Promise<", { "pluginId": "@kbn/ml-agg-utils", "scope": "server", @@ -198,6 +198,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.fetchAggIntervals.$7", + "type": "Object", + "tags": [], + "label": "abortSignal", + "description": [], + "signature": [ + "AbortSignal | undefined" + ], + "path": "x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [], @@ -225,7 +240,7 @@ }, ", samplerShardSize: number, runtimeMappings?: ", "MappingRuntimeFields", - " | undefined) => Promise<(", + " | undefined, abortSignal?: AbortSignal | undefined) => Promise<(", "NumericChartData", " | OrdinalChartData | UnsupportedChartData)[]>" ], @@ -341,6 +356,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": false + }, + { + "parentPluginId": "@kbn/ml-agg-utils", + "id": "def-server.fetchHistogramsForFields.$7", + "type": "Object", + "tags": [], + "label": "abortSignal", + "description": [], + "signature": [ + "AbortSignal | undefined" + ], + "path": "x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [ diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index cca86fdea568f..f975e0adcf2dd 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact Machine Learning UI for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 64 | 2 | 44 | 3 | +| 66 | 2 | 46 | 3 | ## Server diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 4583213488325..961a4452bb2eb 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: 2022-10-20 +date: 2022-10-27 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_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index d871b9f5f84c5..0c344b5cfa6da 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 4a1ba8cb9d2bd..a73369d84cdc5 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index c4b6c3fc990ad..9af26a966a2bf 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: 2022-10-20 +date: 2022-10-27 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 f2b169e94fb49..3739d7fef07d1 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: 2022-10-20 +date: 2022-10-27 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 c352eff4d2f00..3a551f5fe0893 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: 2022-10-20 +date: 2022-10-27 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_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 51cb17a1a38e3..39fe312208428 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: 2022-10-20 +date: 2022-10-27 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_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 8d7793bca3e89..dab05c3a8542e 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: 2022-10-20 +date: 2022-10-27 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 0068b1ada744d..a4273b7613503 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index 5ebe317a643d5..7a373678f9a02 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 8d2eb036e35ab..0ebac27d67bfb 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.devdocs.json b/api_docs/kbn_rule_data_utils.devdocs.json index adb2f74e16015..bde52540e8d2c 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -957,7 +957,7 @@ "label": "AlertStatus", "description": [], "signature": [ - "\"active\" | \"recovered\"" + "\"recovered\" | \"active\"" ], "path": "packages/kbn-rule-data-utils/src/alerts_as_data_status.ts", "deprecated": false, diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 033714495c220..91e1cf13a225b 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 8dc48bb6441e6..8700c9e8a4c5e 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 8c8a1150a65ac..1421325227eca 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: 2022-10-20 +date: 2022-10-27 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 4b8a99506de5e..38fd6d15bb426 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: 2022-10-20 +date: 2022-10-27 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 8e96142a2edf6..1e36f71d583e6 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: 2022-10-20 +date: 2022-10-27 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.devdocs.json b/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json index f9869d88c91cd..12a248d480308 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.devdocs.json @@ -62,82 +62,58 @@ "misc": [ { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.Action", + "id": "def-common.ConcurrentSearches", "type": "Type", "tags": [], - "label": "Action", + "label": "ConcurrentSearches", "description": [], "signature": [ - "{ group: string; id: string; action_type_id: string; params: ", - { - "pluginId": "@kbn/securitysolution-io-ts-alerting-types", - "scope": "common", - "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" - }, - "; }" + "number" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/threat_mapping/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.Actions", + "id": "def-common.ConcurrentSearchesOrUndefined", "type": "Type", "tags": [], - "label": "Actions", + "label": "ConcurrentSearchesOrUndefined", "description": [], "signature": [ - "{ group: string; id: string; action_type_id: string; params: ", - { - "pluginId": "@kbn/securitysolution-io-ts-alerting-types", - "scope": "common", - "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" - }, - "; }[]" + "number | undefined" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/threat_mapping/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.ActionsCamel", + "id": "def-common.ItemsPerSearch", "type": "Type", "tags": [], - "label": "ActionsCamel", + "label": "ItemsPerSearch", "description": [], "signature": [ - "{ group: string; id: string; action_type_id: string; params: ", - { - "pluginId": "@kbn/securitysolution-io-ts-alerting-types", - "scope": "common", - "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" - }, - "; }[]" + "number" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/threat_mapping/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.ConcurrentSearches", + "id": "def-common.ItemsPerSearchOrUndefined", "type": "Type", "tags": [], - "label": "ConcurrentSearches", + "label": "ItemsPerSearchOrUndefined", "description": [], "signature": [ - "number" + "number | undefined" ], "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/threat_mapping/index.ts", "deprecated": false, @@ -146,271 +122,391 @@ }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.ConcurrentSearchesOrUndefined", + "id": "def-common.Language", "type": "Type", "tags": [], - "label": "ConcurrentSearchesOrUndefined", + "label": "Language", "description": [], "signature": [ - "number | undefined" + "\"eql\" | \"lucene\" | \"kuery\"" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/threat_mapping/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/language/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.From", + "id": "def-common.LanguageOrUndefined", "type": "Type", "tags": [], - "label": "From", + "label": "LanguageOrUndefined", "description": [], "signature": [ - "string" + "\"eql\" | \"lucene\" | \"kuery\" | undefined" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/from/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/language/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.FromOrUndefined", + "id": "def-common.MachineLearningJobId", "type": "Type", "tags": [], - "label": "FromOrUndefined", + "label": "MachineLearningJobId", "description": [], "signature": [ - "string | undefined" + "string | string[]" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/from/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/machine_learning_job_id/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.ItemsPerSearch", + "id": "def-common.MachineLearningJobIdNormalized", "type": "Type", "tags": [], - "label": "ItemsPerSearch", + "label": "MachineLearningJobIdNormalized", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/normalized_ml_job_id/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.MachineLearningJobIdNormalizedOrUndefined", + "type": "Type", + "tags": [], + "label": "MachineLearningJobIdNormalizedOrUndefined", + "description": [], + "signature": [ + "string[] | undefined" + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/normalized_ml_job_id/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.MachineLearningJobIdOrUndefined", + "type": "Type", + "tags": [], + "label": "MachineLearningJobIdOrUndefined", + "description": [], + "signature": [ + "string | string[] | undefined" + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/machine_learning_job_id/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.MaxSignals", + "type": "Type", + "tags": [], + "label": "MaxSignals", "description": [], "signature": [ "number" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/threat_mapping/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/max_signals/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.ItemsPerSearchOrUndefined", + "id": "def-common.MaxSignalsOrUndefined", "type": "Type", "tags": [], - "label": "ItemsPerSearchOrUndefined", + "label": "MaxSignalsOrUndefined", "description": [], "signature": [ "number | undefined" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/threat_mapping/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/max_signals/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.Language", + "id": "def-common.RiskScore", "type": "Type", "tags": [], - "label": "Language", - "description": [], + "label": "RiskScore", + "description": [ + "\nTypes the risk score as:\n - Natural Number (positive integer and not a float),\n - Between the values [0 and 100] inclusive." + ], "signature": [ - "\"eql\" | \"lucene\" | \"kuery\"" + "number" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/language/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.LanguageOrUndefined", + "id": "def-common.RiskScoreMapping", "type": "Type", "tags": [], - "label": "LanguageOrUndefined", + "label": "RiskScoreMapping", "description": [], "signature": [ - "\"eql\" | \"lucene\" | \"kuery\" | undefined" + "{ field: string; value: string; operator: \"equals\"; risk_score: number | undefined; }[]" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/language/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.MachineLearningJobId", + "id": "def-common.RiskScoreMappingItem", "type": "Type", "tags": [], - "label": "MachineLearningJobId", + "label": "RiskScoreMappingItem", "description": [], "signature": [ - "string | string[]" + "{ field: string; value: string; operator: \"equals\"; risk_score: number | undefined; }" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/machine_learning_job_id/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.MachineLearningJobIdNormalized", + "id": "def-common.RuleAction", "type": "Type", "tags": [], - "label": "MachineLearningJobIdNormalized", + "label": "RuleAction", "description": [], "signature": [ - "string[]" + "{ group: string; id: string; action_type_id: string; params: ", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + "; }" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/normalized_ml_job_id/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.MachineLearningJobIdNormalizedOrUndefined", + "id": "def-common.RuleActionArray", "type": "Type", "tags": [], - "label": "MachineLearningJobIdNormalizedOrUndefined", + "label": "RuleActionArray", "description": [], "signature": [ - "string[] | undefined" + "{ group: string; id: string; action_type_id: string; params: ", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + "; }[]" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/normalized_ml_job_id/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.MachineLearningJobIdOrUndefined", + "id": "def-common.RuleActionArrayCamel", "type": "Type", "tags": [], - "label": "MachineLearningJobIdOrUndefined", + "label": "RuleActionArrayCamel", "description": [], "signature": [ - "string | string[] | undefined" + "{ group: string; id: string; actionTypeId: string; params: ", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + "; }[]" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/machine_learning_job_id/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.MaxSignals", + "id": "def-common.RuleActionCamel", "type": "Type", "tags": [], - "label": "MaxSignals", + "label": "RuleActionCamel", "description": [], "signature": [ - "number" + "{ group: string; id: string; actionTypeId: string; params: ", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + "; }" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/max_signals/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.MaxSignalsOrUndefined", + "id": "def-common.RuleActionGroup", "type": "Type", "tags": [], - "label": "MaxSignalsOrUndefined", + "label": "RuleActionGroup", "description": [], "signature": [ - "number | undefined" + "string" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/max_signals/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.RiskScore", + "id": "def-common.RuleActionId", "type": "Type", "tags": [], - "label": "RiskScore", + "label": "RuleActionId", "description": [], "signature": [ - "number" + "string" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.RiskScoreC", + "id": "def-common.RuleActionParams", + "type": "Type", + "tags": [ + "see" + ], + "label": "RuleActionParams", + "description": [ + "\nParams is an \"object\", since it is a type of RuleActionParams which is action templates." + ], + "signature": [ + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + } + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.RuleActionThrottle", "type": "Type", "tags": [], - "label": "RiskScoreC", + "label": "RuleActionThrottle", "description": [], "signature": [ - "Type", - "" + "string" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.RiskScoreMapping", + "id": "def-common.RuleActionTypeId", "type": "Type", "tags": [], - "label": "RiskScoreMapping", + "label": "RuleActionTypeId", "description": [], "signature": [ - "{ field: string; value: string; operator: \"equals\"; risk_score: number | undefined; }[]" + "string" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.RiskScoreMappingOrUndefined", + "id": "def-common.RuleInterval", "type": "Type", "tags": [], - "label": "RiskScoreMappingOrUndefined", + "label": "RuleInterval", "description": [], "signature": [ - "{ field: string; value: string; operator: \"equals\"; risk_score: number | undefined; }[] | undefined" + "string" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/rule_schedule/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.RiskScoreOrUndefined", + "id": "def-common.RuleIntervalFrom", "type": "Type", "tags": [], - "label": "RiskScoreOrUndefined", + "label": "RuleIntervalFrom", "description": [], "signature": [ - "number | undefined" + "string" + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/rule_schedule/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.RuleIntervalTo", + "type": "Type", + "tags": [], + "label": "RuleIntervalTo", + "description": [ + "\nTODO: Create a regular expression type or custom date math part type here" + ], + "signature": [ + "string" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/rule_schedule/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -517,36 +613,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.SeverityMappingOrUndefined", - "type": "Type", - "tags": [], - "label": "SeverityMappingOrUndefined", - "description": [], - "signature": [ - "{ field: string; operator: \"equals\"; value: string; severity: \"medium\" | \"high\" | \"low\" | \"critical\"; }[] | undefined" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/severity_mapping/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.SeverityOrUndefined", - "type": "Type", - "tags": [], - "label": "SeverityOrUndefined", - "description": [], - "signature": [ - "\"medium\" | \"high\" | \"low\" | \"critical\" | undefined" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/severity/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", "id": "def-common.Threat", @@ -847,36 +913,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.Throttle", - "type": "Type", - "tags": [], - "label": "Throttle", - "description": [], - "signature": [ - "string" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.ThrottleOrNull", - "type": "Type", - "tags": [], - "label": "ThrottleOrNull", - "description": [], - "signature": [ - "string | null" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", "id": "def-common.Type", @@ -897,229 +933,18 @@ "id": "def-common.TypeOrUndefined", "type": "Type", "tags": [], - "label": "TypeOrUndefined", - "description": [], - "signature": [ - "\"query\" | \"eql\" | \"threshold\" | \"machine_learning\" | \"saved_query\" | \"threat_match\" | \"new_terms\" | undefined" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/type/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ], - "objects": [ - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.action", - "type": "Object", - "tags": [], - "label": "action", - "description": [], - "signature": [ - "ExactC", - "<", - "TypeC", - "<{ group: ", - "StringC", - "; id: ", - "StringC", - "; action_type_id: ", - "StringC", - "; params: ", - "Type", - "<", - { - "pluginId": "@kbn/securitysolution-io-ts-alerting-types", - "scope": "common", - "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" - }, - ", ", - { - "pluginId": "@kbn/securitysolution-io-ts-alerting-types", - "scope": "common", - "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" - }, - ", unknown>; }>>" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.action_action_type_id", - "type": "Object", - "tags": [], - "label": "action_action_type_id", - "description": [], - "signature": [ - "StringC" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.action_group", - "type": "Object", - "tags": [ - "see" - ], - "label": "action_group", - "description": [ - "\nParams is an \"object\", since it is a type of RuleActionParams which is action templates." - ], - "signature": [ - "StringC" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.action_id", - "type": "Object", - "tags": [], - "label": "action_id", - "description": [], - "signature": [ - "StringC" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.action_params", - "type": "Object", - "tags": [], - "label": "action_params", - "description": [], - "signature": [ - "Type", - "<", - { - "pluginId": "@kbn/securitysolution-io-ts-alerting-types", - "scope": "common", - "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" - }, - ", ", - { - "pluginId": "@kbn/securitysolution-io-ts-alerting-types", - "scope": "common", - "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" - }, - ", unknown>" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.actions", - "type": "Object", - "tags": [], - "label": "actions", - "description": [], - "signature": [ - "ArrayC", - "<", - "ExactC", - "<", - "TypeC", - "<{ group: ", - "StringC", - "; id: ", - "StringC", - "; action_type_id: ", - "StringC", - "; params: ", - "Type", - "<", - { - "pluginId": "@kbn/securitysolution-io-ts-alerting-types", - "scope": "common", - "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" - }, - ", ", - { - "pluginId": "@kbn/securitysolution-io-ts-alerting-types", - "scope": "common", - "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" - }, - ", unknown>; }>>>" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.actionsCamel", - "type": "Object", - "tags": [], - "label": "actionsCamel", - "description": [], - "signature": [ - "ArrayC", - "<", - "ExactC", - "<", - "TypeC", - "<{ group: ", - "StringC", - "; id: ", - "StringC", - "; actionTypeId: ", - "StringC", - "; params: ", - "Type", - "<", - { - "pluginId": "@kbn/securitysolution-io-ts-alerting-types", - "scope": "common", - "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" - }, - ", ", - { - "pluginId": "@kbn/securitysolution-io-ts-alerting-types", - "scope": "common", - "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", - "section": "def-common.SavedObjectAttributes", - "text": "SavedObjectAttributes" - }, - ", unknown>; }>>>" + "label": "TypeOrUndefined", + "description": [], + "signature": [ + "\"query\" | \"eql\" | \"threshold\" | \"machine_learning\" | \"saved_query\" | \"threat_match\" | \"new_terms\" | undefined" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/type/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false - }, + } + ], + "objects": [ { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", "id": "def-common.concurrent_searches", @@ -1321,7 +1146,7 @@ "tags": [], "label": "DefaultRiskScoreMappingArray", "description": [ - "\nTypes the DefaultStringArray as:\n - If null or undefined, then a default risk_score_mapping array will be set" + "\nTypes the DefaultStringArray as:\n - If null or undefined, then a default RiskScoreMapping array will be set" ], "signature": [ "Type", @@ -1339,7 +1164,7 @@ "tags": [], "label": "DefaultSeverityMappingArray", "description": [ - "\nTypes the DefaultStringArray as:\n - If null or undefined, then a default severity_mapping array will be set" + "\nTypes the DefaultStringArray as:\n - If null or undefined, then a default SeverityMapping array will be set" ], "signature": [ "Type", @@ -1404,42 +1229,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.from", - "type": "Object", - "tags": [], - "label": "from", - "description": [], - "signature": [ - "Type", - "" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/from/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.fromOrUndefined", - "type": "Object", - "tags": [], - "label": "fromOrUndefined", - "description": [], - "signature": [ - "UnionC", - "<[", - "Type", - ", ", - "UndefinedC", - "]>" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/from/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", "id": "def-common.items_per_search", @@ -1639,191 +1428,404 @@ ], "signature": [ "Type", - "" + "" + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/references_default_array/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.RiskScore", + "type": "Object", + "tags": [], + "label": "RiskScore", + "description": [], + "signature": [ + "Type", + "" + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.RiskScoreMapping", + "type": "Object", + "tags": [], + "label": "RiskScoreMapping", + "description": [], + "signature": [ + "ArrayC", + "<", + "ExactC", + "<", + "TypeC", + "<{ field: ", + "StringC", + "; value: ", + "StringC", + "; operator: ", + "KeyofC", + "<{ equals: null; }>; risk_score: ", + "UnionC", + "<[", + "Type", + ", ", + "UndefinedC", + "]>; }>>>" + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.RiskScoreMappingItem", + "type": "Object", + "tags": [], + "label": "RiskScoreMappingItem", + "description": [], + "signature": [ + "ExactC", + "<", + "TypeC", + "<{ field: ", + "StringC", + "; value: ", + "StringC", + "; operator: ", + "KeyofC", + "<{ equals: null; }>; risk_score: ", + "UnionC", + "<[", + "Type", + ", ", + "UndefinedC", + "]>; }>>" + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.RuleAction", + "type": "Object", + "tags": [], + "label": "RuleAction", + "description": [], + "signature": [ + "ExactC", + "<", + "TypeC", + "<{ group: ", + "StringC", + "; id: ", + "StringC", + "; action_type_id: ", + "StringC", + "; params: ", + "Type", + "<", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + ", ", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + ", unknown>; }>>" + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.RuleActionArray", + "type": "Object", + "tags": [], + "label": "RuleActionArray", + "description": [], + "signature": [ + "ArrayC", + "<", + "ExactC", + "<", + "TypeC", + "<{ group: ", + "StringC", + "; id: ", + "StringC", + "; action_type_id: ", + "StringC", + "; params: ", + "Type", + "<", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + ", ", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + ", unknown>; }>>>" + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.RuleActionArrayCamel", + "type": "Object", + "tags": [], + "label": "RuleActionArrayCamel", + "description": [], + "signature": [ + "ArrayC", + "<", + "ExactC", + "<", + "TypeC", + "<{ group: ", + "StringC", + "; id: ", + "StringC", + "; actionTypeId: ", + "StringC", + "; params: ", + "Type", + "<", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + ", ", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + ", unknown>; }>>>" + ], + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", + "id": "def-common.RuleActionCamel", + "type": "Object", + "tags": [], + "label": "RuleActionCamel", + "description": [], + "signature": [ + "ExactC", + "<", + "TypeC", + "<{ group: ", + "StringC", + "; id: ", + "StringC", + "; actionTypeId: ", + "StringC", + "; params: ", + "Type", + "<", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + ", ", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + ", unknown>; }>>" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/references_default_array/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.risk_score", + "id": "def-common.RuleActionGroup", "type": "Object", "tags": [], - "label": "risk_score", + "label": "RuleActionGroup", "description": [], "signature": [ - "Type", - "" + "StringC" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.risk_score_mapping", + "id": "def-common.RuleActionId", "type": "Object", "tags": [], - "label": "risk_score_mapping", + "label": "RuleActionId", "description": [], "signature": [ - "ArrayC", - "<", - "ExactC", - "<", - "TypeC", - "<{ field: ", - "StringC", - "; value: ", - "StringC", - "; operator: ", - "KeyofC", - "<{ equals: null; }>; risk_score: ", - "UnionC", - "<[", - "Type", - ", ", - "UndefinedC", - "]>; }>>>" + "StringC" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.risk_score_mapping_field", + "id": "def-common.RuleActionParams", "type": "Object", "tags": [], - "label": "risk_score_mapping_field", + "label": "RuleActionParams", "description": [], "signature": [ - "StringC" + "Type", + "<", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + ", ", + { + "pluginId": "@kbn/securitysolution-io-ts-alerting-types", + "scope": "common", + "docId": "kibKbnSecuritysolutionIoTsAlertingTypesPluginApi", + "section": "def-common.SavedObjectAttributes", + "text": "SavedObjectAttributes" + }, + ", unknown>" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.risk_score_mapping_item", + "id": "def-common.RuleActionThrottle", "type": "Object", "tags": [], - "label": "risk_score_mapping_item", + "label": "RuleActionThrottle", "description": [], "signature": [ - "ExactC", - "<", - "TypeC", - "<{ field: ", - "StringC", - "; value: ", - "StringC", - "; operator: ", - "KeyofC", - "<{ equals: null; }>; risk_score: ", "UnionC", "<[", + "LiteralC", + "<\"no_actions\">, ", + "LiteralC", + "<\"rule\">, ", "Type", - ", ", - "UndefinedC", - "]>; }>>" + "]>" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.risk_score_mapping_value", + "id": "def-common.RuleActionTypeId", "type": "Object", "tags": [], - "label": "risk_score_mapping_value", + "label": "RuleActionTypeId", "description": [], "signature": [ "StringC" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.RiskScore", + "id": "def-common.RuleInterval", "type": "Object", "tags": [], - "label": "RiskScore", - "description": [ - "\nTypes the risk score as:\n - Natural Number (positive integer and not a float),\n - Between the values [0 and 100] inclusive." - ], + "label": "RuleInterval", + "description": [], "signature": [ - "Type", - "" + "StringC" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/rule_schedule/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.riskScoreMappingOrUndefined", + "id": "def-common.RuleIntervalFrom", "type": "Object", "tags": [], - "label": "riskScoreMappingOrUndefined", + "label": "RuleIntervalFrom", "description": [], "signature": [ - "UnionC", - "<[", - "ArrayC", - "<", - "ExactC", - "<", - "TypeC", - "<{ field: ", - "StringC", - "; value: ", - "StringC", - "; operator: ", - "KeyofC", - "<{ equals: null; }>; risk_score: ", - "UnionC", - "<[", "Type", - ", ", - "UndefinedC", - "]>; }>>>, ", - "UndefinedC", - "]>" + "" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/rule_schedule/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.riskScoreOrUndefined", + "id": "def-common.RuleIntervalTo", "type": "Object", "tags": [], - "label": "riskScoreOrUndefined", + "label": "RuleIntervalTo", "description": [], "signature": [ - "UnionC", - "<[", - "Type", - ", ", - "UndefinedC", - "]>" + "StringC" ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts", + "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/rule_schedule/index.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -1926,10 +1928,10 @@ }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.severity", + "id": "def-common.Severity", "type": "Object", "tags": [], - "label": "severity", + "label": "Severity", "description": [], "signature": [ "KeyofC", @@ -1942,10 +1944,10 @@ }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.severity_mapping", + "id": "def-common.SeverityMapping", "type": "Object", "tags": [], - "label": "severity_mapping", + "label": "SeverityMapping", "description": [], "signature": [ "ArrayC", @@ -1970,25 +1972,10 @@ }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.severity_mapping_field", - "type": "Object", - "tags": [], - "label": "severity_mapping_field", - "description": [], - "signature": [ - "StringC" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/severity_mapping/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.severity_mapping_item", + "id": "def-common.SeverityMappingItem", "type": "Object", "tags": [], - "label": "severity_mapping_item", + "label": "SeverityMappingItem", "description": [], "signature": [ "ExactC", @@ -2009,73 +1996,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.severity_mapping_value", - "type": "Object", - "tags": [], - "label": "severity_mapping_value", - "description": [], - "signature": [ - "StringC" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/severity_mapping/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.severityMappingOrUndefined", - "type": "Object", - "tags": [], - "label": "severityMappingOrUndefined", - "description": [], - "signature": [ - "UnionC", - "<[", - "ArrayC", - "<", - "ExactC", - "<", - "TypeC", - "<{ field: ", - "StringC", - "; operator: ", - "KeyofC", - "<{ equals: null; }>; value: ", - "StringC", - "; severity: ", - "KeyofC", - "<{ low: null; medium: null; high: null; critical: null; }>; }>>>, ", - "UndefinedC", - "]>" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/severity_mapping/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.severityOrUndefined", - "type": "Object", - "tags": [], - "label": "severityOrUndefined", - "description": [], - "signature": [ - "UnionC", - "<[", - "KeyofC", - "<{ low: null; medium: null; high: null; critical: null; }>, ", - "UndefinedC", - "]>" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/severity/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", "id": "def-common.threat", @@ -2880,54 +2800,6 @@ "trackAdoption": false, "initialIsOpen": false }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.throttle", - "type": "Object", - "tags": [], - "label": "throttle", - "description": [], - "signature": [ - "UnionC", - "<[", - "LiteralC", - "<\"no_actions\">, ", - "LiteralC", - "<\"rule\">, ", - "Type", - "]>" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", - "id": "def-common.throttleOrNull", - "type": "Object", - "tags": [], - "label": "throttleOrNull", - "description": [], - "signature": [ - "UnionC", - "<[", - "UnionC", - "<[", - "LiteralC", - "<\"no_actions\">, ", - "LiteralC", - "<\"rule\">, ", - "Type", - "]>, ", - "NullC", - "]>" - ], - "path": "packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "@kbn/securitysolution-io-ts-alerting-types", "id": "def-common.type", diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 2ff12c9112da2..59b33d3511299 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 145 | 0 | 127 | 0 | +| 138 | 0 | 119 | 0 | ## Common diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 20107e0bfebe3..ec17673720285 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: 2022-10-20 +date: 2022-10-27 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 2a216d0b905ba..1a7c9d4e3e3ec 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: 2022-10-20 +date: 2022-10-27 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 7cca0d51bd71e..9bdd8bf2fdfcf 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: 2022-10-20 +date: 2022-10-27 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 d2b9f3cd245a1..aa11e861c185c 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: 2022-10-20 +date: 2022-10-27 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 871f2b2db8c90..89d2a42208cb6 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: 2022-10-20 +date: 2022-10-27 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 7f6630077abfb..df230d069323e 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: 2022-10-20 +date: 2022-10-27 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 7751c0035784d..51d7dcb313441 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: 2022-10-20 +date: 2022-10-27 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 9d46a6e8cad73..9f217f89910a5 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: 2022-10-20 +date: 2022-10-27 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 4df40e4cbe930..9d4c93b381232 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: 2022-10-20 +date: 2022-10-27 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 c3383e4c9411c..0eae5ed3cc6bd 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: 2022-10-20 +date: 2022-10-27 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 ab57e8d242200..a0c2843feb485 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: 2022-10-20 +date: 2022-10-27 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 bf6152a48a54d..540d0b120c7c4 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 56e03380cc56b..e3ef1838c580a 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: 2022-10-20 +date: 2022-10-27 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_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 140e23a1266a2..473457102bbe2 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 9716e8ba14c22..772c22bf6f3dc 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 41337273a50cb..c242fbbdf3e23 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: 2022-10-20 +date: 2022-10-27 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 80e87ef6dd32e..2b4cb3c1300e1 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: 2022-10-20 +date: 2022-10-27 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 303ed560827a0..ed538f0f19bef 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: 2022-10-20 +date: 2022-10-27 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_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 4ac3160e4658d..e9f801ccfd8a1 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: 2022-10-20 +date: 2022-10-27 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 1d4b83c8060ce..2bbbd1a44b820 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: 2022-10-20 +date: 2022-10-27 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 1955749c7826b..87a7a99ebb6a7 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: 2022-10-20 +date: 2022-10-27 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 22db67bddcaaf..625678a76d86f 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: 2022-10-20 +date: 2022-10-27 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 86d9ac7012e3f..1eaac38267408 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: 2022-10-20 +date: 2022-10-27 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 f52d68ffcc71b..b747c92281a13 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: 2022-10-20 +date: 2022-10-27 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 39cef6b5fc57c..8c717b1f396d4 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: 2022-10-20 +date: 2022-10-27 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 de815ae124749..d33db8b876a70 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: 2022-10-20 +date: 2022-10-27 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 282ec7408052e..23a5a4745149c 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: 2022-10-20 +date: 2022-10-27 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 1474210b24c62..c67224718bed8 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: 2022-10-20 +date: 2022-10-27 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 a62444bf18004..1089b4de5732d 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: 2022-10-20 +date: 2022-10-27 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 179cee9710ef0..d223d911d4d3c 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: 2022-10-20 +date: 2022-10-27 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 ee94a7b74aeeb..f66c631487e94 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: 2022-10-20 +date: 2022-10-27 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 f5e7c916d598e..443e347881498 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: 2022-10-20 +date: 2022-10-27 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 1925b8b7a32e3..06b521722dd1b 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: 2022-10-20 +date: 2022-10-27 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 0ffd939d8e58c..38ebbe56715cb 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: 2022-10-20 +date: 2022-10-27 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_router.mdx b/api_docs/kbn_shared_ux_router.mdx index 6fa35ce2197fd..ff77a93f41a32 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: 2022-10-20 +date: 2022-10-27 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 a6225ec43a674..ae8dd4550531c 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: 2022-10-20 +date: 2022-10-27 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 2d8c1e2886c83..a2cffb01929a1 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: 2022-10-20 +date: 2022-10-27 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 16ccbcffa0c9f..e168d27763943 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: 2022-10-20 +date: 2022-10-27 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_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index dbbc180ea9c08..73b58d0e58431 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index 8d5b317a6c907..2027933ae6b53 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: 2022-10-20 +date: 2022-10-27 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_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index e82ba2edca4cf..8c09347bc9c3c 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index 2f6004c4b1522..297e65d5d104a 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: 2022-10-20 +date: 2022-10-27 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 675596c2bdc0f..d7d05aaa61e4a 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: 2022-10-20 +date: 2022-10-27 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 c57a38112b054..92c7f062e2568 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 897e00022340e..826b8156b68ed 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: 2022-10-20 +date: 2022-10-27 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 d11c349b2fb98..790282f8889ff 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index c65cd7f816cd9..739935fa250a4 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: 2022-10-20 +date: 2022-10-27 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 e2fc560c16162..c9d6e62f0564e 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index aada5cc7d17b7..ca984b5dd648e 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index 925c8aa757080..f42dc731221e1 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index 26fdfff440799..9027787fe4002 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index d01b35439e310..54c42b5300b3d 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: 2022-10-20 +date: 2022-10-27 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_theme.mdx b/api_docs/kbn_ui_theme.mdx index cb12b4b4ec9f5..4d7924bca7ef1 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 816569aae53f0..b23721515b9b9 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: 2022-10-20 +date: 2022-10-27 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 cafcc24b0d5ab..adafffa3d6001 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: 2022-10-20 +date: 2022-10-27 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 85d65d1002f19..9c8079bbe74c6 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: 2022-10-20 +date: 2022-10-27 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 1b8aa0bc07226..2a7427c92aaf0 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 6273e86b4f1b7..68c90b1f162c2 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 498ada3897f18..d5b57a7c2821f 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: 2022-10-20 +date: 2022-10-27 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 7c738714a4cc9..4b693f7a2a4cc 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: 2022-10-20 +date: 2022-10-27 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 f3542cf74b5d3..cd85dbae95427 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: 2022-10-20 +date: 2022-10-27 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 d2decc779ebe7..4cbb2c132a1e2 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: 2022-10-20 +date: 2022-10-27 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 01f255cfbf6c4..fb2f5a68611e2 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -35,7 +35,7 @@ }, ", ", "LensEmbeddableOutput", - "> implements ", + ", any> implements ", { "pluginId": "embeddable", "scope": "public", @@ -2053,6 +2053,20 @@ "path": "x-pack/plugins/lens/public/datasources/form_based/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "lens", + "id": "def-public.FormBasedLayer.sampling", + "type": "number", + "tags": [], + "label": "sampling", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "x-pack/plugins/lens/public/datasources/form_based/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -5102,7 +5116,7 @@ "signature": [ "((layerId: string, state: T, setState: ", "StateSetter", - ") => ", + ", openLayerSettings?: (() => void) | undefined) => ", "LayerAction", "[]) | undefined" ], @@ -5155,6 +5169,21 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.getSupportedActionsForLayer.$4", + "type": "Function", + "tags": [], + "label": "openLayerSettings", + "description": [], + "signature": [ + "(() => void) | undefined" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [] @@ -5781,6 +5810,56 @@ ], "returnComment": [] }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.renderLayerSettings", + "type": "Function", + "tags": [], + "label": "renderLayerSettings", + "description": [], + "signature": [ + "((domElement: Element, props: ", + "VisualizationLayerSettingsProps", + ") => void | ((cleanupElement: Element) => void)) | undefined" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "lens", + "id": "def-public.Visualization.renderLayerSettings.$1", + "type": "Object", + "tags": [], + "label": "domElement", + "description": [], + "signature": [ + "Element" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "lens", + "id": "def-public.Visualization.renderLayerSettings.$2", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "VisualizationLayerSettingsProps", + "" + ], + "path": "x-pack/plugins/lens/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "lens", "id": "def-public.Visualization.renderDimensionEditor", @@ -8980,7 +9059,7 @@ "section": "def-public.IncompleteColumn", "text": "IncompleteColumn" }, - "> | undefined; }" + "> | undefined; sampling?: number | undefined; }" ], "path": "x-pack/plugins/lens/public/datasources/form_based/types.ts", "deprecated": false, @@ -9385,13 +9464,7 @@ "text": "LensServerPlugin" }, " implements ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, + "Plugin", "<", { "pluginId": "lens", @@ -9431,13 +9504,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "<", { "pluginId": "lens", @@ -9492,13 +9559,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "<", { "pluginId": "lens", @@ -9547,13 +9608,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ", plugins: ", { "pluginId": "lens", @@ -9576,13 +9631,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], "path": "x-pack/plugins/lens/server/plugin.tsx", "deprecated": false, diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 0740af861e6e2..69861c54024cc 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 669 | 0 | 576 | 46 | +| 674 | 0 | 581 | 47 | ## Client diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index d8453576b74d8..d7dbd837b77c5 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: 2022-10-20 +date: 2022-10-27 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 b0c4620ca6010..2dd396e494dbd 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.devdocs.json b/api_docs/licensing.devdocs.json index 6b2d792de9c75..df3ae7c6518ff 100644 --- a/api_docs/licensing.devdocs.json +++ b/api_docs/licensing.devdocs.json @@ -598,6 +598,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/endpoint/routes/actions/list.test.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/routes/actions/list.test.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts" @@ -1871,6 +1875,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/endpoint/routes/actions/list.test.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/routes/actions/list.test.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts" diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 83e3b64a75521..b674e1d2d3cfe 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 708db749853df..b5287376c9796 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 638ae8b0b503b..aba0560e1bf7c 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.devdocs.json b/api_docs/maps.devdocs.json index 752becaa2ceb6..2350f3b292bc0 100644 --- a/api_docs/maps.devdocs.json +++ b/api_docs/maps.devdocs.json @@ -200,7 +200,7 @@ "section": "def-public.MapEmbeddableOutput", "text": "MapEmbeddableOutput" }, - "> implements ", + ", any> implements ", { "pluginId": "embeddable", "scope": "public", diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index afbf090e71206..d91fe6db2ce47 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.devdocs.json b/api_docs/maps_ems.devdocs.json index ef117f9582be4..3479c443f5f13 100644 --- a/api_docs/maps_ems.devdocs.json +++ b/api_docs/maps_ems.devdocs.json @@ -433,13 +433,7 @@ "text": "MapsEmsPlugin" }, " implements ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, + "Plugin", "<", { "pluginId": "mapsEms", @@ -462,13 +456,7 @@ "label": "_initializerContext", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, + "PluginInitializerContext", "; }>; includeElasticMapsService: boolean; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { dark: string; bright: string; desaturated: string; }>; }>>" ], "path": "src/plugins/maps_ems/server/index.ts", @@ -497,13 +485,7 @@ "label": "initializerContext", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, + "PluginInitializerContext", "; }>; includeElasticMapsService: boolean; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { dark: string; bright: string; desaturated: string; }>; }>>" ], "path": "src/plugins/maps_ems/server/index.ts", @@ -523,13 +505,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", ", plugins: MapsEmsSetupServerDependencies) => { config: Readonly<{} & { tilemap: Readonly<{ url?: string | undefined; } & { options: Readonly<{ default?: boolean | undefined; tileSize?: number | undefined; subdomains?: string[] | undefined; errorTileUrl?: string | undefined; tms?: boolean | undefined; reuseTiles?: boolean | undefined; bounds?: number[] | undefined; } & { attribution: string; minZoom: number; maxZoom: number; }>; }>; includeElasticMapsService: boolean; emsUrl: string; emsFileApiUrl: string; emsTileApiUrl: string; emsLandingPageUrl: string; emsFontLibraryUrl: string; emsTileLayerId: Readonly<{} & { dark: string; bright: string; desaturated: string; }>; }>; createEMSSettings: () => ", { "pluginId": "mapsEms", @@ -552,13 +528,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "" ], "path": "src/plugins/maps_ems/server/index.ts", diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 8a47a6888f9df..fb61737234619 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.devdocs.json b/api_docs/ml.devdocs.json index 792bc38f914f3..e407bdfa53682 100644 --- a/api_docs/ml.devdocs.json +++ b/api_docs/ml.devdocs.json @@ -639,6 +639,19 @@ "path": "x-pack/plugins/ml/common/types/anomalies.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "ml", + "id": "def-public.AnomaliesTableRecord.modelPlotEnabled", + "type": "boolean", + "tags": [], + "label": "modelPlotEnabled", + "description": [ + "\nReturns true if the job has the model plot enabled" + ], + "path": "x-pack/plugins/ml/common/types/anomalies.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -2066,6 +2079,19 @@ "path": "x-pack/plugins/ml/common/types/anomalies.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "ml", + "id": "def-server.AnomaliesTableRecord.modelPlotEnabled", + "type": "boolean", + "tags": [], + "label": "modelPlotEnabled", + "description": [ + "\nReturns true if the job has the model plot enabled" + ], + "path": "x-pack/plugins/ml/common/types/anomalies.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -2434,6 +2460,22 @@ "path": "x-pack/plugins/ml/common/types/anomalies.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "ml", + "id": "def-server.AnomalyRecordDoc.anomaly_score_explanation", + "type": "Object", + "tags": [], + "label": "anomaly_score_explanation", + "description": [ + "\nAn explanation for the anomaly score" + ], + "signature": [ + "{ anomaly_type?: \"dip\" | \"spike\" | undefined; anomaly_length?: number | undefined; single_bucket_impact?: number | undefined; multi_bucket_impact?: number | undefined; anomaly_characteristics_impact?: number | undefined; lower_confidence_bound?: number | undefined; typical_value?: number | undefined; upper_confidence_bound?: number | undefined; high_variance_penalty?: boolean | undefined; incomplete_bucket_penalty?: boolean | undefined; } | undefined" + ], + "path": "x-pack/plugins/ml/common/types/anomalies.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index a8746ba91e913..4676f44f78871 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) for q | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 251 | 9 | 78 | 39 | +| 254 | 9 | 78 | 39 | ## Client diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 20883bc3e1da9..9fe0edd8ff4dc 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: 2022-10-20 +date: 2022-10-27 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 fcfa7e1b1d21b..71a7f13031ad8 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: 2022-10-20 +date: 2022-10-27 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 e5e3b97593b55..829c455f7c3ae 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: 2022-10-20 +date: 2022-10-27 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 4b4cec9beebaa..82bf25ca27072 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index 1358da9170942..c9d8b2c0def40 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -368,7 +368,7 @@ "label": "ExploratoryViewContextProvider", "description": [], "signature": [ - "({\n children,\n reportTypes,\n dataTypes,\n dataViews,\n reportConfigMap,\n setHeaderActionMenu,\n asPanel = true,\n theme$,\n}: { children: JSX.Element; } & ExploratoryViewContextValue) => JSX.Element" + "({\n children,\n reportTypes,\n dataTypes,\n reportConfigMap,\n setHeaderActionMenu,\n asPanel = true,\n theme$,\n}: { children: JSX.Element; } & ExploratoryViewContextValue) => JSX.Element" ], "path": "x-pack/plugins/observability/public/components/shared/exploratory_view/contexts/exploratory_view_config.tsx", "deprecated": false, @@ -379,7 +379,7 @@ "id": "def-public.ExploratoryViewContextProvider.$1", "type": "CompoundType", "tags": [], - "label": "{\n children,\n reportTypes,\n dataTypes,\n dataViews,\n reportConfigMap,\n setHeaderActionMenu,\n asPanel = true,\n theme$,\n}", + "label": "{\n children,\n reportTypes,\n dataTypes,\n reportConfigMap,\n setHeaderActionMenu,\n asPanel = true,\n theme$,\n}", "description": [], "signature": [ "{ children: JSX.Element; } & ExploratoryViewContextValue" @@ -7550,21 +7550,9 @@ "description": [], "signature": [ "{ start: () => Promise<", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ">; setup: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "; }" ], "path": "x-pack/plugins/observability/server/routes/types.ts", @@ -7780,7 +7768,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: \"occurrences\"; objective: { target: number; }; summary: { sli_value: number; error_budget: { initial: number; consumed: number; remaining: number; }; }; revision: number; created_at: string; updated_at: string; }, ", + ", { id: string; name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; } | { type: \"slo.kql.custom\"; params: { index: string; query_filter: string; numerator: string; denominator: string; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; summary: { sli_value: number; error_budget: { initial: number; consumed: number; remaining: number; }; }; revision: number; created_at: string; updated_at: string; }, ", { "pluginId": "observability", "scope": "server", @@ -7808,7 +7796,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_duration\">; params: ", "TypeC", "<{ environment: ", "UnionC", @@ -7840,7 +7828,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_error_rate\">; params: ", "IntersectionC", "<[", "TypeC", @@ -7882,7 +7870,21 @@ "LiteralC", "<\"4xx\">, ", "LiteralC", - "<\"5xx\">]>>; }>]>; }>]>; time_window: ", + "<\"5xx\">]>>; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"slo.kql.custom\">; params: ", + "TypeC", + "<{ index: ", + "StringC", + "; query_filter: ", + "StringC", + "; numerator: ", + "StringC", + "; denominator: ", + "StringC", + "; }>; }>]>; time_window: ", "UnionC", "<[", "TypeC", @@ -7903,12 +7905,26 @@ "<{ start_time: ", "Type", "; }>; }>]>; budgeting_method: ", + "UnionC", + "<[", + "LiteralC", + ", ", "LiteralC", - "<\"occurrences\">; objective: ", + "]>; objective: ", + "IntersectionC", + "<[", "TypeC", "<{ target: ", "NumberC", - "; }>; }>; }>, ", + "; }>, ", + "PartialC", + "<{ timeslice_target: ", + "NumberC", + "; timeslice_window: ", + "Type", + "<", + "Duration", + ", string, unknown>; }>]>; }>; }>, ", { "pluginId": "observability", "scope": "server", @@ -7916,7 +7932,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: \"occurrences\"; objective: { target: number; }; created_at: string; updated_at: string; }, ", + ", { id: string; name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; } | { type: \"slo.kql.custom\"; params: { index: string; query_filter: string; numerator: string; denominator: string; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; created_at: string; updated_at: string; }, ", { "pluginId": "observability", "scope": "server", @@ -7940,7 +7956,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_duration\">; params: ", "TypeC", "<{ environment: ", "UnionC", @@ -7972,7 +7988,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_error_rate\">; params: ", "IntersectionC", "<[", "TypeC", @@ -8014,7 +8030,21 @@ "LiteralC", "<\"4xx\">, ", "LiteralC", - "<\"5xx\">]>>; }>]>; }>]>; time_window: ", + "<\"5xx\">]>>; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"slo.kql.custom\">; params: ", + "TypeC", + "<{ index: ", + "StringC", + "; query_filter: ", + "StringC", + "; numerator: ", + "StringC", + "; denominator: ", + "StringC", + "; }>; }>]>; time_window: ", "UnionC", "<[", "TypeC", @@ -8035,12 +8065,26 @@ "<{ start_time: ", "Type", "; }>; }>]>; budgeting_method: ", + "UnionC", + "<[", + "LiteralC", + ", ", "LiteralC", - "<\"occurrences\">; objective: ", + "]>; objective: ", + "IntersectionC", + "<[", "TypeC", "<{ target: ", "NumberC", - "; }>; }>; }>, ", + "; }>, ", + "PartialC", + "<{ timeslice_target: ", + "NumberC", + "; timeslice_window: ", + "Type", + "<", + "Duration", + ", string, unknown>; }>]>; }>; }>, ", { "pluginId": "observability", "scope": "server", @@ -8158,7 +8202,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: \"occurrences\"; objective: { target: number; }; summary: { sli_value: number; error_budget: { initial: number; consumed: number; remaining: number; }; }; revision: number; created_at: string; updated_at: string; }, ", + ", { id: string; name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; } | { type: \"slo.kql.custom\"; params: { index: string; query_filter: string; numerator: string; denominator: string; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; summary: { sli_value: number; error_budget: { initial: number; consumed: number; remaining: number; }; }; revision: number; created_at: string; updated_at: string; }, ", { "pluginId": "observability", "scope": "server", @@ -8186,7 +8230,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_duration\">; params: ", "TypeC", "<{ environment: ", "UnionC", @@ -8218,7 +8262,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_error_rate\">; params: ", "IntersectionC", "<[", "TypeC", @@ -8260,7 +8304,21 @@ "LiteralC", "<\"4xx\">, ", "LiteralC", - "<\"5xx\">]>>; }>]>; }>]>; time_window: ", + "<\"5xx\">]>>; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"slo.kql.custom\">; params: ", + "TypeC", + "<{ index: ", + "StringC", + "; query_filter: ", + "StringC", + "; numerator: ", + "StringC", + "; denominator: ", + "StringC", + "; }>; }>]>; time_window: ", "UnionC", "<[", "TypeC", @@ -8281,12 +8339,26 @@ "<{ start_time: ", "Type", "; }>; }>]>; budgeting_method: ", + "UnionC", + "<[", "LiteralC", - "<\"occurrences\">; objective: ", + ", ", + "LiteralC", + "]>; objective: ", + "IntersectionC", + "<[", "TypeC", "<{ target: ", "NumberC", - "; }>; }>; }>, ", + "; }>, ", + "PartialC", + "<{ timeslice_target: ", + "NumberC", + "; timeslice_window: ", + "Type", + "<", + "Duration", + ", string, unknown>; }>]>; }>; }>, ", { "pluginId": "observability", "scope": "server", @@ -8294,7 +8366,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: string; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: \"occurrences\"; objective: { target: number; }; created_at: string; updated_at: string; }, ", + ", { id: string; name: string; description: string; indicator: { type: \"slo.apm.transaction_duration\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; 'threshold.us': number; }; } | { type: \"slo.apm.transaction_error_rate\"; params: { environment: string; service: string; transaction_type: string; transaction_name: string; } & { good_status_codes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; }; } | { type: \"slo.kql.custom\"; params: { index: string; query_filter: string; numerator: string; denominator: string; }; }; time_window: { duration: string; is_rolling: boolean; } | { duration: string; calendar: { start_time: string; }; }; budgeting_method: string; objective: { target: number; } & { timeslice_target?: number | undefined; timeslice_window?: string | undefined; }; created_at: string; updated_at: string; }, ", { "pluginId": "observability", "scope": "server", @@ -8318,7 +8390,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_duration\">; params: ", "TypeC", "<{ environment: ", "UnionC", @@ -8350,7 +8422,7 @@ "TypeC", "<{ type: ", "LiteralC", - "; params: ", + "<\"slo.apm.transaction_error_rate\">; params: ", "IntersectionC", "<[", "TypeC", @@ -8392,7 +8464,21 @@ "LiteralC", "<\"4xx\">, ", "LiteralC", - "<\"5xx\">]>>; }>]>; }>]>; time_window: ", + "<\"5xx\">]>>; }>]>; }>, ", + "TypeC", + "<{ type: ", + "LiteralC", + "<\"slo.kql.custom\">; params: ", + "TypeC", + "<{ index: ", + "StringC", + "; query_filter: ", + "StringC", + "; numerator: ", + "StringC", + "; denominator: ", + "StringC", + "; }>; }>]>; time_window: ", "UnionC", "<[", "TypeC", @@ -8413,12 +8499,26 @@ "<{ start_time: ", "Type", "; }>; }>]>; budgeting_method: ", + "UnionC", + "<[", + "LiteralC", + ", ", "LiteralC", - "<\"occurrences\">; objective: ", + "]>; objective: ", + "IntersectionC", + "<[", "TypeC", "<{ target: ", "NumberC", - "; }>; }>; }>, ", + "; }>, ", + "PartialC", + "<{ timeslice_target: ", + "NumberC", + "; timeslice_window: ", + "Type", + "<", + "Duration", + ", string, unknown>; }>]>; }>; }>, ", { "pluginId": "observability", "scope": "server", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 01ffb938b6418..f7521758efba9 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 609be776bb796..bd8a1d7090a48 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index db802862e289f..b33b622ccd952 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,23 +15,23 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 496 | 411 | 38 | +| 503 | 416 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 32608 | 179 | 21917 | 1032 | +| 32804 | 179 | 21997 | 1039 | ## Plugin Directory | Plugin name           | Maintaining team | Description | API Cnt | Any Cnt | Missing
comments | Missing
exports | |--------------|----------------|-----------|--------------|----------|---------------|--------| -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 214 | 0 | 209 | 23 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 225 | 0 | 220 | 24 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 36 | 1 | 32 | 2 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 9 | 0 | 0 | 2 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 382 | 0 | 373 | 24 | -| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 38 | 0 | 38 | 53 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 382 | 0 | 373 | 26 | +| | [APM UI](https://github.com/orgs/elastic/teams/apm-ui) | The user interface for Elastic APM | 38 | 0 | 38 | 56 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 81 | 1 | 72 | 2 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Canvas application to Kibana | 9 | 0 | 8 | 3 | @@ -45,22 +45,22 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | cloudLinks | [Kibana Core](https://github.com/orgs/elastic/teams/@kibana-core) | Adds the links to the Elastic Cloud console | 0 | 0 | 0 | 0 | | | [Cloud Security Posture](https://github.com/orgs/elastic/teams/cloud-posture-security) | The cloud security posture plugin | 18 | 0 | 2 | 3 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 13 | 0 | 13 | 1 | -| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 229 | 0 | 220 | 7 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2693 | 0 | 23 | 0 | +| | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 233 | 0 | 224 | 7 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2700 | 0 | 0 | 0 | | crossClusterReplication | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 107 | 0 | 88 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 121 | 0 | 114 | 3 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 52 | 0 | 51 | 0 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3242 | 33 | 2516 | 24 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 3251 | 33 | 2523 | 24 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | This plugin provides the ability to create data views via a modal flyout inside Kibana apps | 16 | 0 | 7 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Reusable data view field editor across Kibana | 60 | 0 | 30 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data view management app | 2 | 0 | 2 | 0 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1020 | 0 | 229 | 2 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1021 | 0 | 229 | 2 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 28 | 3 | 24 | 1 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 10 | 0 | 8 | 2 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 97 | 0 | 80 | 4 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 37 | 0 | 35 | 2 | -| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 512 | 0 | 412 | 4 | +| | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds embeddables service to Kibana | 510 | 0 | 410 | 4 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends embeddable plugin with more functionality | 14 | 0 | 14 | 0 | | | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 51 | 0 | 42 | 0 | | | [Enterprise Search](https://github.com/orgs/elastic/teams/enterprise-search-frontend) | Adds dashboards for discovering and managing Enterprise Search products. | 9 | 0 | 9 | 0 | @@ -81,17 +81,17 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression Tagcloud plugin adds a `tagcloud` renderer and function to the expression plugin. The renderer will display the `Wordcloud` chart. | 7 | 0 | 7 | 0 | | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Expression XY plugin adds a `xy` renderer and function to the expression plugin. The renderer will display the `xy` chart. | 159 | 0 | 149 | 9 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds expression runtime to Kibana | 2191 | 17 | 1734 | 5 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 222 | 0 | 95 | 2 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 227 | 0 | 96 | 2 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Index pattern fields and ambiguous values formatters | 288 | 5 | 249 | 3 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | 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. | 62 | 0 | 62 | 2 | -| | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 260 | 0 | 14 | 2 | +| | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 271 | 0 | 18 | 2 | | | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 996 | 3 | 893 | 17 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | globalSearchProviders | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | graph | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 0 | 0 | 0 | 0 | | grokdebugger | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | -| | [Journey Onboarding](https://github.com/orgs/elastic/teams/platform-onboarding) | Guided onboarding framework | 19 | 0 | 19 | 3 | +| | [Journey Onboarding](https://github.com/orgs/elastic/teams/platform-onboarding) | Guided onboarding framework | 36 | 0 | 36 | 1 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 143 | 0 | 104 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 4 | 0 | 4 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 177 | 0 | 172 | 3 | @@ -105,7 +105,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | kibanaUsageCollection | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 0 | 0 | 0 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 615 | 3 | 418 | 9 | | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 3 | 0 | 3 | 1 | -| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 669 | 0 | 576 | 46 | +| | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. Exposes components to embed visualizations and link into the Lens editor from within other apps in Kibana. | 674 | 0 | 581 | 47 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 8 | 0 | 8 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 3 | 0 | 3 | 0 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 117 | 0 | 42 | 10 | @@ -114,7 +114,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Vis Editors](https://github.com/orgs/elastic/teams/kibana-vis-editors) | - | 41 | 0 | 41 | 6 | | | [GIS](https://github.com/orgs/elastic/teams/kibana-gis) | - | 263 | 0 | 262 | 26 | | | [GIS](https://github.com/orgs/elastic/teams/kibana-gis) | - | 67 | 0 | 67 | 0 | -| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 251 | 9 | 78 | 39 | +| | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 254 | 9 | 78 | 39 | | | [Stack Monitoring](https://github.com/orgs/elastic/teams/stack-monitoring-ui) | - | 11 | 0 | 9 | 1 | | | [Stack Monitoring](https://github.com/orgs/elastic/teams/stack-monitoring-ui) | - | 9 | 0 | 9 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 34 | 0 | 34 | 2 | @@ -233,7 +233,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 5 | 0 | 0 | 0 | | | Kibana Core | - | 16 | 0 | 7 | 0 | | | Kibana Core | - | 6 | 0 | 6 | 0 | -| | Kibana Core | - | 114 | 0 | 41 | 0 | +| | Kibana Core | - | 119 | 0 | 43 | 0 | | | Kibana Core | - | 3 | 0 | 3 | 0 | | | Kibana Core | - | 4 | 0 | 4 | 0 | | | Kibana Core | - | 9 | 0 | 3 | 0 | @@ -288,6 +288,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 4 | 0 | 4 | 0 | | | Kibana Core | - | 28 | 0 | 0 | 0 | | | Kibana Core | - | 5 | 0 | 5 | 0 | +| | Kibana Core | - | 31 | 0 | 0 | 0 | +| | Kibana Core | - | 9 | 0 | 9 | 0 | | | Kibana Core | - | 56 | 0 | 30 | 0 | | | Kibana Core | - | 9 | 0 | 5 | 1 | | | Kibana Core | - | 13 | 0 | 12 | 0 | @@ -308,12 +310,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 3 | 0 | 3 | 0 | | | Kibana Core | - | 14 | 0 | 10 | 0 | | | Kibana Core | - | 6 | 0 | 6 | 0 | +| | Kibana Core | - | 58 | 0 | 26 | 0 | +| | Kibana Core | - | 5 | 0 | 5 | 0 | | | Kibana Core | - | 5 | 0 | 0 | 0 | | | Kibana Core | - | 6 | 0 | 6 | 0 | | | Kibana Core | - | 2 | 0 | 2 | 0 | | | Kibana Core | - | 2 | 0 | 2 | 0 | | | Kibana Core | - | 4 | 0 | 4 | 1 | -| | Kibana Core | - | 106 | 1 | 75 | 0 | +| | Kibana Core | - | 107 | 1 | 76 | 0 | | | Kibana Core | - | 310 | 1 | 137 | 0 | | | Kibana Core | - | 71 | 0 | 51 | 0 | | | Kibana Core | - | 6 | 0 | 6 | 0 | @@ -322,12 +326,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | Kibana Core | - | 2 | 0 | 1 | 0 | | | Kibana Core | - | 6 | 0 | 6 | 0 | | | Kibana Core | - | 7 | 0 | 7 | 0 | -| | Kibana Core | - | 82 | 0 | 41 | 0 | +| | Kibana Core | - | 83 | 0 | 41 | 0 | | | Kibana Core | - | 25 | 0 | 23 | 0 | | | Kibana Core | - | 4 | 0 | 4 | 0 | | | Kibana Core | - | 100 | 0 | 74 | 43 | | | Kibana Core | - | 12 | 0 | 12 | 0 | -| | Kibana Core | - | 225 | 0 | 82 | 0 | +| | Kibana Core | - | 226 | 0 | 83 | 0 | | | Kibana Core | - | 69 | 0 | 69 | 4 | | | Kibana Core | - | 14 | 0 | 13 | 0 | | | Kibana Core | - | 99 | 1 | 86 | 0 | @@ -373,6 +377,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 28 | 0 | 28 | 2 | | | [Owner missing] | - | 1 | 0 | 0 | 0 | | | [Owner missing] | - | 3 | 0 | 0 | 0 | +| | [Owner missing] | - | 17 | 0 | 17 | 2 | | | [Owner missing] | - | 6 | 0 | 0 | 0 | | | [Owner missing] | - | 3 | 0 | 3 | 0 | | | [Owner missing] | - | 32 | 0 | 22 | 0 | @@ -384,12 +389,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 13 | 0 | 13 | 0 | | | [Owner missing] | - | 64 | 0 | 59 | 5 | | | [Owner missing] | - | 96 | 0 | 95 | 0 | -| | [Owner missing] | - | 5 | 0 | 4 | 0 | +| | [Owner missing] | - | 7 | 0 | 5 | 0 | | | Kibana Core | - | 30 | 0 | 5 | 37 | | | Kibana Core | - | 8 | 0 | 8 | 0 | | | [Owner missing] | - | 6 | 0 | 1 | 1 | | | [Owner missing] | - | 534 | 1 | 1 | 0 | -| | Machine Learning UI | This package includes utility functions related to creating elasticsearch aggregation queries, data manipulation and verification. | 64 | 2 | 44 | 3 | +| | Machine Learning UI | This package includes utility functions related to creating elasticsearch aggregation queries, data manipulation and verification. | 66 | 2 | 46 | 3 | | | Machine Learning UI | A type guard to check record like object structures. | 3 | 0 | 2 | 0 | | | Machine Learning UI | Creates a deterministic number based hash out of a string. | 2 | 0 | 1 | 0 | | | [Owner missing] | - | 55 | 0 | 55 | 2 | @@ -406,7 +411,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | security solution elastic search utilities to use across plugins such lists, security_solution, cases, etc... | 67 | 0 | 61 | 1 | | | [Owner missing] | - | 89 | 0 | 78 | 1 | | | [Owner missing] | Security Solution utilities for React hooks | 15 | 0 | 7 | 0 | -| | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 145 | 0 | 127 | 0 | +| | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 138 | 0 | 119 | 0 | | | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 505 | 1 | 492 | 0 | | | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 65 | 0 | 36 | 0 | | | [Owner missing] | io ts utilities and types to be shared with plugins from the security solution project | 28 | 0 | 21 | 0 | diff --git a/api_docs/presentation_util.devdocs.json b/api_docs/presentation_util.devdocs.json index 9b4fae824a02c..7a250188fd990 100644 --- a/api_docs/presentation_util.devdocs.json +++ b/api_docs/presentation_util.devdocs.json @@ -2613,7 +2613,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - "; initialComponentState?: ReduxEmbeddableStateType[\"componentState\"] | undefined; syncSettings?: ", + "; initialComponentState?: ReduxEmbeddableStateType[\"componentState\"] | undefined; syncSettings?: ", "ReduxEmbeddableSyncSettings", "<", { @@ -2670,7 +2670,7 @@ "section": "def-public.IEmbeddable", "text": "IEmbeddable" }, - "; initialComponentState?: ReduxEmbeddableStateType[\"componentState\"] | undefined; syncSettings?: ", + "; initialComponentState?: ReduxEmbeddableStateType[\"componentState\"] | undefined; syncSettings?: ", "ReduxEmbeddableSyncSettings", "<", { diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 5b8b880834a50..ce40f6a9731df 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: 2022-10-20 +date: 2022-10-27 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 71bfc40d13c6d..ffae14439f459 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 091c3c5273a98..950847ee149ca 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: 2022-10-20 +date: 2022-10-27 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 201e3e126917d..7c070d808186d 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: 2022-10-20 +date: 2022-10-27 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 5b7d8209a2b21..7bb6fb89cc4ca 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index d05ef1264b7cd..1b69ab9be749a 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: 2022-10-20 +date: 2022-10-27 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 05185c5d28fff..743a82e9a2dd2 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: 2022-10-20 +date: 2022-10-27 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 9b737c4798aeb..d6b1dd0234466 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: 2022-10-20 +date: 2022-10-27 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 41ca713edf881..cff827c69b9ef 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: 2022-10-20 +date: 2022-10-27 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 2e072a64eabff..e131778d3904f 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: 2022-10-20 +date: 2022-10-27 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 f28f79ea92de6..483b4fcb62e4a 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: 2022-10-20 +date: 2022-10-27 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 c0359745698d3..cc14564308e63 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: 2022-10-20 +date: 2022-10-27 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 0c004f91d99f2..77970589d361d 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: 2022-10-20 +date: 2022-10-27 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 15f2ee1360887..06815f8335cb4 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: 2022-10-20 +date: 2022-10-27 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 6f067a7c98157..85dc9d9046c38 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.devdocs.json b/api_docs/security.devdocs.json index 6d6efd0ecc1a5..331e2d40aeb93 100644 --- a/api_docs/security.devdocs.json +++ b/api_docs/security.devdocs.json @@ -2584,6 +2584,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/request_context_factory.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts" @@ -2600,10 +2604,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts" - }, { "plugin": "cloudChat", "path": "x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.ts" diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 2e24aa109b279..0cff94e1dc566 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index db510724f2984..b2794eb58fe05 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -1390,13 +1390,7 @@ "text": "Plugin" }, " implements ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, + "Plugin", "<", { "pluginId": "securitySolution", @@ -1445,13 +1439,7 @@ "label": "context", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, + "PluginInitializerContext", "" ], "path": "x-pack/plugins/security_solution/server/plugin.ts", @@ -1529,13 +1517,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ", plugins: ", "SecuritySolutionPluginStartDependencies", ") => ", @@ -1559,13 +1541,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], "path": "x-pack/plugins/security_solution/server/plugin.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 57eb1dd7bd5b7..2e8307de6f176 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 76bd26074e5da..5907661a277f4 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: 2022-10-20 +date: 2022-10-27 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 bffe945bf2c57..181f7915dd280 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 8165049a46396..3961744180e2e 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: 2022-10-20 +date: 2022-10-27 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 aca9d5bd01a8a..a88d0c575f4b4 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: 2022-10-20 +date: 2022-10-27 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 da477d71d6fa1..66faad2e7bdb8 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: 2022-10-20 +date: 2022-10-27 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 b41b4f0efde63..b2a40c81f24f2 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.devdocs.json b/api_docs/task_manager.devdocs.json index 41d8c4273d8de..cdc661b92a91b 100644 --- a/api_docs/task_manager.devdocs.json +++ b/api_docs/task_manager.devdocs.json @@ -26,13 +26,7 @@ "text": "TaskManagerPlugin" }, " implements ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, + "Plugin", "<", { "pluginId": "taskManager", @@ -77,13 +71,7 @@ "label": "initContext", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, + "PluginInitializerContext", "" ], "path": "x-pack/plugins/task_manager/server/plugin.ts", @@ -103,13 +91,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", ", plugins: { usageCollection?: ", { "pluginId": "usageCollection", @@ -139,13 +121,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "" ], "path": "x-pack/plugins/task_manager/server/plugin.ts", @@ -199,13 +175,7 @@ "description": [], "signature": [ "({ savedObjects, elasticsearch, executionContext, }: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ") => ", { "pluginId": "taskManager", @@ -227,13 +197,7 @@ "label": "{\n savedObjects,\n elasticsearch,\n executionContext,\n }", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], "path": "x-pack/plugins/task_manager/server/plugin.ts", "deprecated": false, @@ -1534,7 +1498,9 @@ "TaskScheduling", ", \"schedule\" | \"runSoon\" | \"ephemeralRunNow\" | \"ensureScheduled\" | \"bulkUpdateSchedules\" | \"bulkEnable\" | \"bulkDisable\" | \"bulkSchedule\"> & Pick<", "TaskStore", - ", \"get\" | \"aggregate\" | \"fetch\" | \"remove\"> & { removeIfExists: (id: string) => Promise; } & { supportsEphemeralTasks: () => boolean; }" + ", \"get\" | \"aggregate\" | \"fetch\" | \"remove\"> & { removeIfExists: (id: string) => Promise; } & { bulkRemoveIfExist: (ids: string[]) => Promise<", + "SavedObjectsBulkDeleteResponse", + " | undefined>; } & { supportsEphemeralTasks: () => boolean; }" ], "path": "x-pack/plugins/task_manager/server/plugin.ts", "deprecated": false, diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index c32aa226c432e..4a2f037a62908 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: 2022-10-20 +date: 2022-10-27 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 85a430b935469..375b8a20ddb6c 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: 2022-10-20 +date: 2022-10-27 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 e78d7ab4bfa69..5c665aa41c8d1 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: 2022-10-20 +date: 2022-10-27 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 b6cf490064ce9..cb364a70043b7 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: 2022-10-20 +date: 2022-10-27 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 9759255750d43..6a2b6cbdb3f10 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: 2022-10-20 +date: 2022-10-27 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 6f665ce6ee569..bf513d9715539 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.devdocs.json b/api_docs/timelines.devdocs.json index 23859b9ee23aa..5d590998cb26b 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -3222,7 +3222,7 @@ }, "; description?: string | null | undefined; esTypes?: string[] | undefined; example?: string | number | null | undefined; format?: string | undefined; linkField?: string | undefined; placeholder?: string | undefined; subType?: ", "IFieldSubType", - " | undefined; type?: string | undefined; })[]; readonly filters?: ", + " | undefined; type?: string | undefined; })[]; readonly title?: string | undefined; readonly filters?: ", "Filter", "[] | undefined; readonly dataViewId: string | null; readonly sort: ", "SortColumnTable", diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 6b447d178739e..bf9379ef8b081 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: 2022-10-20 +date: 2022-10-27 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 f794633f1997d..95f51743910c5 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 825ac42f72426..6de961837296f 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index d4ded7aed8b67..71ab21795d1e3 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.devdocs.json b/api_docs/ui_actions_enhanced.devdocs.json index 7d029f3b88c45..fb7773659555d 100644 --- a/api_docs/ui_actions_enhanced.devdocs.json +++ b/api_docs/ui_actions_enhanced.devdocs.json @@ -3656,13 +3656,7 @@ "text": "AdvancedUiActionsServerPlugin" }, " implements ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, + "Plugin", "<", { "pluginId": "uiActionsEnhanced", @@ -3732,13 +3726,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", ", { embeddable }: SetupDependencies) => { registerActionFactory: (definition: ", { "pluginId": "uiActionsEnhanced", @@ -3769,13 +3757,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "" ], "path": "src/plugins/ui_actions_enhanced/server/plugin.ts", diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index fd2a3664fc5d1..d583e50ce35df 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 937c4b59659eb..c6cd331100e2f 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index 0e09d95e86fd7..9307ca4f2e23f 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.devdocs.json b/api_docs/unified_search.devdocs.json index 8f42fc8d88284..4ee81970fe4c7 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -452,7 +452,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | undefined" + ", any> | undefined" ], "path": "src/plugins/unified_search/public/actions/apply_filter_action.ts", "deprecated": false, @@ -1904,13 +1904,7 @@ "text": "UnifiedSearchServerPlugin" }, " implements ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.Plugin", - "text": "Plugin" - }, + "Plugin", "<", { "pluginId": "unifiedSearch", @@ -1947,13 +1941,7 @@ "label": "initializerContext", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.PluginInitializerContext", - "text": "PluginInitializerContext" - }, + "PluginInitializerContext", "; valueSuggestions: Readonly<{} & { timeout: moment.Duration; enabled: boolean; tiers: string[]; terminateAfter: moment.Duration; }>; }>; }>>" ], "path": "src/plugins/unified_search/server/plugin.ts", @@ -1973,13 +1961,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "<", "UnifiedSearchServerPluginStartDependencies", ", ", @@ -2006,13 +1988,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreSetup", - "text": "CoreSetup" - }, + "CoreSetup", "<", "UnifiedSearchServerPluginStartDependencies", ", ", @@ -2057,13 +2033,7 @@ "description": [], "signature": [ "(core: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - }, + "CoreStart", ", {}: ", "UnifiedSearchServerPluginStartDependencies", ") => {}" @@ -2080,13 +2050,7 @@ "label": "core", "description": [], "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.CoreStart", - "text": "CoreStart" - } + "CoreStart" ], "path": "src/plugins/unified_search/server/plugin.ts", "deprecated": false, diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 269e37770a6ca..c30024c987f18 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: 2022-10-20 +date: 2022-10-27 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 7b7785b8c5498..4cdf0604fce49 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 1f61340ddd192..2b7f8f4c348c7 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: 2022-10-20 +date: 2022-10-27 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 0d2bd5167d822..dff18eb622be0 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: 2022-10-20 +date: 2022-10-27 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 e398f431a47b3..5f068af205279 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: 2022-10-20 +date: 2022-10-27 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 6900b279dcbd2..ed04d200d187b 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: 2022-10-20 +date: 2022-10-27 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 9d8cb0d2fa124..b691fbc749e23 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: 2022-10-20 +date: 2022-10-27 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 7783f96a0b550..c3f84534fac5c 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: 2022-10-20 +date: 2022-10-27 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 6afbff496daa5..090136b4acb74 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: 2022-10-20 +date: 2022-10-27 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 96cd26d718932..5e466bb97f00d 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: 2022-10-20 +date: 2022-10-27 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 9bc4580049e58..d6931853fad20 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: 2022-10-20 +date: 2022-10-27 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 656d5801c4051..20ed2a608f2e5 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: 2022-10-20 +date: 2022-10-27 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 e638fae2c0efd..76e49206309f3 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: 2022-10-20 +date: 2022-10-27 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 72bb6b5272221..a9de284f9266e 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: 2022-10-20 +date: 2022-10-27 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 1ad8363497f3e..482291d6a57ce 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index b4885bafc9d79..fdf936320467b 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -1320,7 +1320,7 @@ }, ", ", "VisualizeOutput", - "> implements ", + ", any> implements ", { "pluginId": "embeddable", "scope": "public", @@ -1815,13 +1815,13 @@ }, { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEmbeddable.renderError", + "id": "def-public.VisualizeEmbeddable.catchError", "type": "Function", "tags": [], - "label": "renderError", + "label": "catchError", "description": [], "signature": [ - "(domNode: HTMLElement, error: string | ", + "(error: string | ", { "pluginId": "expressions", "scope": "common", @@ -1829,7 +1829,7 @@ "section": "def-common.ErrorLike", "text": "ErrorLike" }, - ") => () => boolean" + ") => JSX.Element" ], "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", "deprecated": false, @@ -1837,22 +1837,7 @@ "children": [ { "parentPluginId": "visualizations", - "id": "def-public.VisualizeEmbeddable.renderError.$1", - "type": "Object", - "tags": [], - "label": "domNode", - "description": [], - "signature": [ - "HTMLElement" - ], - "path": "src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "visualizations", - "id": "def-public.VisualizeEmbeddable.renderError.$2", + "id": "def-public.VisualizeEmbeddable.catchError.$1", "type": "CompoundType", "tags": [], "label": "error", @@ -6198,7 +6183,7 @@ "section": "def-public.ExpressionRenderError", "text": "ExpressionRenderError" }, - ") => void; renderError: (domNode: HTMLElement, error: string | ", + ") => void; catchError: (error: string | ", { "pluginId": "expressions", "scope": "common", @@ -6206,7 +6191,7 @@ "section": "def-common.ErrorLike", "text": "ErrorLike" }, - ") => () => boolean; reload: () => Promise; supportedTriggers: () => string[]; inputIsRefType: (input: ", + ") => JSX.Element; reload: () => Promise; supportedTriggers: () => string[]; inputIsRefType: (input: ", { "pluginId": "visualizations", "scope": "public", @@ -6320,7 +6305,7 @@ "section": "def-public.EmbeddableOutput", "text": "EmbeddableOutput" }, - "> | ", + ", any> | ", { "pluginId": "embeddable", "scope": "public", @@ -8487,6 +8472,23 @@ "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "visualizations", + "id": "def-common.ColumnState.palette", + "type": "Object", + "tags": [], + "label": "palette", + "description": [], + "signature": [ + "PaletteOutput", + "<", + "CustomPaletteParams", + "> | undefined" + ], + "path": "src/plugins/visualizations/common/convert_to_lens/types/configurations.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 82b067ad5ea8a..ee708b91ea6b8 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: 2022-10-20 +date: 2022-10-27 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/docs/api-generated/cases/case-apis-passthru.asciidoc b/docs/api-generated/cases/case-apis-passthru.asciidoc index d10933c5b0a9c..2ccee8484b882 100644 --- a/docs/api-generated/cases/case-apis-passthru.asciidoc +++ b/docs/api-generated/cases/case-apis-passthru.asciidoc @@ -87,6 +87,11 @@ Any modifications made to this file will be overwritten. "totalAlerts" : 0, "closed_at" : "2000-01-23T04:56:07.000+00:00", "comments" : [ null, null ], + "assignees" : [ { + "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + }, { + "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + } ], "created_at" : "2022-05-13T09:16:17.416Z", "description" : "A case description.", "title" : "Case title 1", @@ -199,6 +204,11 @@ Any modifications made to this file will be overwritten. "totalAlerts" : 0, "closed_at" : "2000-01-23T04:56:07.000+00:00", "comments" : [ null, null ], + "assignees" : [ { + "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + }, { + "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + } ], "created_at" : "2022-05-13T09:16:17.416Z", "description" : "A case description.", "title" : "Case title 1", @@ -376,6 +386,11 @@ Any modifications made to this file will be overwritten. "totalAlerts" : 0, "closed_at" : "2000-01-23T04:56:07.000+00:00", "comments" : [ null, null ], + "assignees" : [ { + "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + }, { + "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + } ], "created_at" : "2022-05-13T09:16:17.416Z", "description" : "A case description.", "title" : "Case title 1", @@ -488,6 +503,11 @@ Any modifications made to this file will be overwritten. "totalAlerts" : 0, "closed_at" : "2000-01-23T04:56:07.000+00:00", "comments" : [ null, null ], + "assignees" : [ { + "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + }, { + "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + } ], "created_at" : "2022-05-13T09:16:17.416Z", "description" : "A case description.", "title" : "Case title 1", @@ -602,6 +622,11 @@ Any modifications made to this file will be overwritten. "totalAlerts" : 0, "closed_at" : "2000-01-23T04:56:07.000+00:00", "comments" : [ null, null ], + "assignees" : [ { + "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + }, { + "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + } ], "created_at" : "2022-05-13T09:16:17.416Z", "description" : "A case description.", "title" : "Case title 1", @@ -677,6 +702,7 @@ Any modifications made to this file will be overwritten.
  • case_response_closed_by_properties - Case response properties for closed_by
  • case_response_created_by_properties - Case response properties for created_by
  • case_response_properties - Case response properties
  • +
  • case_response_properties_assignees_inner -
  • case_response_pushed_by_properties - Case response properties for pushed_by
  • case_response_updated_by_properties - Case response properties for updated_by
  • connector_properties_cases_webhook - Create or upate case request properties for Cases Webhook connector
  • @@ -692,6 +718,7 @@ Any modifications made to this file will be overwritten.
  • connector_properties_swimlane - Create case request properties for a Swimlane connector
  • connector_properties_swimlane_fields -
  • create_case_request - Create case request
  • +
  • create_case_request_assignees_inner -
  • create_case_request_connector -
  • external_service -
  • owners -
  • @@ -864,7 +891,8 @@ Any modifications made to this file will be overwritten.

    case_response_properties - Case response properties Up

    -
    closed_at
    Date format: date-time
    +
    assignees (optional)
    array[case_response_properties_assignees_inner] An array containing users that are assigned to the case.
    +
    closed_at
    Date format: date-time
    closed_by
    comments
    array[Case_response_properties_for_comments_inner] An array of comment objects for the case.
    connector
    @@ -887,6 +915,13 @@ Any modifications made to this file will be overwritten.
    version
    +
    +

    case_response_properties_assignees_inner - Up

    +
    +
    +
    uid
    String A unique identifier for the user profile. You can use the get user profile API to retrieve more details.
    +
    +

    case_response_pushed_by_properties - Case response properties for pushed_by Up

    @@ -1043,7 +1078,8 @@ Any modifications made to this file will be overwritten.

    create_case_request - Create case request Up

    The create case API request body varies depending on the type of connector.
    -
    connector
    +
    assignees (optional)
    array[create_case_request_assignees_inner] An array containing users that are assigned to the case.
    +
    connector
    description
    String The description for the case.
    owner
    settings
    @@ -1052,6 +1088,13 @@ Any modifications made to this file will be overwritten.
    title
    String A title for the case.
    +
    +

    create_case_request_assignees_inner - Up

    +
    +
    +
    uid
    String A unique identifier for the user profile. These identifiers can be found by using the suggest user profile API.
    +
    +

    create_case_request_connector - Up

    @@ -1152,7 +1195,8 @@ Any modifications made to this file will be overwritten.

    update_case_request_cases_inner - Up

    -
    connector (optional)
    +
    assignees (optional)
    array[create_case_request_assignees_inner] An array containing users that are assigned to the case.
    +
    connector (optional)
    description (optional)
    String An updated description for the case.
    id
    String The identifier for the case.
    settings (optional)
    diff --git a/docs/api/cases/cases-api-create.asciidoc b/docs/api/cases/cases-api-create.asciidoc index 5eec612d8bfca..f124d3500228c 100644 --- a/docs/api/cases/cases-api-create.asciidoc +++ b/docs/api/cases/cases-api-create.asciidoc @@ -34,6 +34,18 @@ default space is used. [role="child_attributes"] === {api-request-body-title} +`assignees`:: +(Optional, array of objects) Array containing users that are assigned to the case. ++ +.Properties of assignee objects +[%collapsible%open] +===== +`uid`:: +(Required, string) A unique identifier for the user profile. These identifiers +can be found by using the +{ref}/security-api-suggest-user-profile.html[suggest user profile API]. +===== + `connector`:: (Required, object) An object that contains the connector configuration. + @@ -207,6 +219,7 @@ the case identifier, version, and creation time. For example: "totalAlerts": 0, "title": "Case title 1", "tags": [ "tag 1" ], + "assignees": [], "settings": { "syncAlerts": true }, diff --git a/docs/api/cases/cases-api-find-cases.asciidoc b/docs/api/cases/cases-api-find-cases.asciidoc index d7ae5fed35b0f..770e1d30ee594 100644 --- a/docs/api/cases/cases-api-find-cases.asciidoc +++ b/docs/api/cases/cases-api-find-cases.asciidoc @@ -141,6 +141,7 @@ The API returns a JSON object listing the retrieved cases. For example: "full_name": "Joe Smith", "username": "jsmith" }, + "assignees": [], "connector": { "id": "none", "name": "none", diff --git a/docs/api/cases/cases-api-get-case.asciidoc b/docs/api/cases/cases-api-get-case.asciidoc index d7bc316a7346d..9d43afb695a79 100644 --- a/docs/api/cases/cases-api-get-case.asciidoc +++ b/docs/api/cases/cases-api-get-case.asciidoc @@ -86,6 +86,7 @@ The API returns a JSON object with the retrieved case. For example: "status":"open", "updated_at":"2022-07-13T15:40:32.335Z", "updated_by":{"full_name":null,"email":null,"username":"elastic"}, + "assignees":[{"uid":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"}], "connector":{"id":"none","name":"none","type":".none","fields":null}, "external_service":null } diff --git a/docs/api/cases/cases-api-update.asciidoc b/docs/api/cases/cases-api-update.asciidoc index 30c482da4282c..ca75e34597afc 100644 --- a/docs/api/cases/cases-api-update.asciidoc +++ b/docs/api/cases/cases-api-update.asciidoc @@ -40,6 +40,19 @@ default space is used. .Properties of `cases` objects [%collapsible%open] ==== + +`assignees`:: +(Optional, array of objects) Array containing users that are assigned to the case. ++ +.Properties of assignee objects +[%collapsible%open] +===== +`uid`:: +(Required, string) A unique identifier for the user profile. These identifiers +can be found by using the +{ref}/security-api-suggest-user-profile.html[suggest user profile API]. +===== + `connector`:: (Optional, object) An object that contains the connector configuration. + @@ -203,6 +216,7 @@ PATCH api/cases }, "description": "A new description.", "tags": [ "tag-1", "tag-2" ], + "assignees": [], "settings": { "syncAlerts": true } diff --git a/docs/index-custom-title-page.html b/docs/index-custom-title-page.html new file mode 100644 index 0000000000000..86fd3a16b3bb3 --- /dev/null +++ b/docs/index-custom-title-page.html @@ -0,0 +1,254 @@ + + +
    + +
    +
    +

    +

    Bring your data to life

    +

    + Kibana is a user interface that lets you visualize your Elasticsearch data and navigate the Elastic Stack. + Take this tutorial for the basics of visualizing data. +

    +

    + + + +

    +

    + What's new + Release notes + How-to videos +

    +
    +
    + +
    +
    + +

    Explore by use case

    + + + +

    Get to know Kibana

    + + + + + +
    +
    +

    + + Manage and secure +

    +
    + +
    + +
    +
    +

    + + Install and upgrade +

    +
    + +
    + +
    +
    +

    + + Tools, APIs, and Dev docs +

    +
    + +
    + + +

    View all Elastic docs

    diff --git a/docs/index-extra-title-page.html b/docs/index-extra-title-page.html deleted file mode 100644 index ced2737984fa5..0000000000000 --- a/docs/index-extra-title-page.html +++ /dev/null @@ -1,153 +0,0 @@ -
    -

    - From creating beautiful visualizations to managing the Elastic Stack, learn how Kibana helps you - get the most of your data. -

    -

    - What's new - Release notes - How-to videos -

    - - - - - - - - - - - - - - - - - - -

    New to Kibana?

    Popular topics

    - - - -

    Analyze your data

    Manage all things Stack

    - - - -
    - -

    All topics

    -
    diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index aa747611bdbaa..8575d9fda01ed 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -107,7 +107,7 @@ You can create spatial filters in two ways: Spatial filters have the following properties: * *Geometry label* enables you to provide a meaningful name for your spatial filter. -* *Spatial relation* determines the {ref}/query-dsl-geo-shape-query.html#_spatial_relations[spatial relation operator] to use at search time. +* *Spatial relation* determines the {ref}/query-dsl-geo-shape-query.html#geo-shape-spatial-relations[spatial relation operator] to use at search time. * *Action* specifies whether to apply the filter to the current view or to a drilldown action. Only available when the map is a panel in a {kibana-ref}/dashboard.html[dashboard] with {kibana-ref}/drilldowns.html[drilldowns]. [role="screenshot"] diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 69316af1593a1..c32d305b22d9a 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -386,6 +386,11 @@ Specifies an array of trusted hostnames, such as the {kib} host, or a reverse proxy sitting in front of it. This determines whether HTTP compression may be used for responses, based on the request `Referer` header. This setting may not be used when <> is set to `false`. *Default: `none`* +`server.compression.brotli.enabled`:: +Set to `true` to enable brotli (br) compression format. +Note: browsers not supporting brotli compression will fallback to using gzip instead. +This setting may not be used when <> is set to `false`. *Default: `false`* + [[server-securityResponseHeaders-strictTransportSecurity]] `server.securityResponseHeaders.strictTransportSecurity`:: Controls whether the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security[`Strict-Transport-Security`] header is used in all responses to the client from the {kib} server, and specifies what value is used. Allowed values are any text value or diff --git a/docs/spaces/index.asciidoc b/docs/spaces/index.asciidoc index 00e50a41b6ce3..9b20d1c23719b 100644 --- a/docs/spaces/index.asciidoc +++ b/docs/spaces/index.asciidoc @@ -19,8 +19,6 @@ image::images/change-space.png["Change current space menu"] The `kibana_admin` role or equivalent is required to manage **Spaces**. -TIP: Looking to support multiple tenants? Refer to <> for more information. - [float] [[spaces-managing]] === View, create, and delete spaces diff --git a/docs/user/security/authorization/index.asciidoc b/docs/user/security/authorization/index.asciidoc index b478e8b7c38d5..7127ebf84ae9b 100644 --- a/docs/user/security/authorization/index.asciidoc +++ b/docs/user/security/authorization/index.asciidoc @@ -6,22 +6,6 @@ The Elastic Stack comes with the `kibana_admin` {ref}/built-in-roles.html[built- When you assign a user multiple roles, the user receives a union of the roles’ privileges. Therefore, assigning the `kibana_admin` role in addition to a custom role that grants {kib} privileges is ineffective because `kibana_admin` has access to all the features in all spaces. -[[xpack-security-multiple-tenants]] -==== Supporting multiple tenants - -There are two approaches to supporting multi-tenancy in {kib}: - -1. *Recommended:* Create a space and a limited role for each tenant, and configure each user with the appropriate role. See -<> for more details. -2. deprecated:[7.13.0,"In 8.0 and later, the `kibana.index` setting will no longer be supported."] Set up separate {kib} instances to work -with a single {es} cluster by changing the `kibana.index` setting in your `kibana.yml` file. -+ -NOTE: When using multiple {kib} instances this way, you cannot use the `kibana_admin` role to grant access. You must create custom roles -that authorize the user for each specific instance. - -Whichever approach you use, be careful when granting cluster privileges and index privileges. Both of these approaches share the same {es} -cluster, and {kib} spaces do not prevent you from granting users of two different tenants access to the same index. - [role="xpack"] [[kibana-role-management]] === {kib} role management diff --git a/examples/guided_onboarding_example/public/components/app.tsx b/examples/guided_onboarding_example/public/components/app.tsx index a5252920c27fa..ae55f3d3811dc 100755 --- a/examples/guided_onboarding_example/public/components/app.tsx +++ b/examples/guided_onboarding_example/public/components/app.tsx @@ -59,7 +59,7 @@ export const GuidedOnboardingExampleApp = (props: GuidedOnboardingExampleAppDeps - + diff --git a/examples/guided_onboarding_example/public/components/main.tsx b/examples/guided_onboarding_example/public/components/main.tsx index a65fd2324d34b..4c9481d423e4c 100644 --- a/examples/guided_onboarding_example/public/components/main.tsx +++ b/examples/guided_onboarding_example/public/components/main.tsx @@ -259,6 +259,7 @@ export const Main = (props: MainProps) => { { value: 'observability', text: 'observability' }, { value: 'security', text: 'security' }, { value: 'search', text: 'search' }, + { value: 'testGuide', text: 'test guide' }, ]} value={selectedGuide} onChange={(e) => { @@ -294,7 +295,7 @@ export const Main = (props: MainProps) => {

    @@ -316,6 +317,14 @@ export const Main = (props: MainProps) => { /> + + history.push('stepThree')}> + + + diff --git a/examples/guided_onboarding_example/public/components/step_one.tsx b/examples/guided_onboarding_example/public/components/step_one.tsx index 3441b4d8e5d99..fd5cb132b6b91 100644 --- a/examples/guided_onboarding_example/public/components/step_one.tsx +++ b/examples/guided_onboarding_example/public/components/step_one.tsx @@ -32,7 +32,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) => const [isTourStepOpen, setIsTourStepOpen] = useState(false); const isTourActive = useObservable( - guidedOnboardingApi!.isGuideStepActive$('search', 'add_data'), + guidedOnboardingApi!.isGuideStepActive$('testGuide', 'step1'), false ); useEffect(() => { @@ -45,7 +45,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>

    @@ -56,7 +56,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>

    @@ -72,12 +72,12 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) => onFinish={() => setIsTourStepOpen(false)} step={1} stepsTotal={1} - title="Step Add data" + title="Step 1" anchorPosition="rightUp" > { - await guidedOnboardingApi?.completeGuideStep('search', 'add_data'); + await guidedOnboardingApi?.completeGuideStep('testGuide', 'step1'); }} > Complete step 1 diff --git a/examples/guided_onboarding_example/public/components/step_three.tsx b/examples/guided_onboarding_example/public/components/step_three.tsx index ffe9d87993611..eefb38165beed 100644 --- a/examples/guided_onboarding_example/public/components/step_three.tsx +++ b/examples/guided_onboarding_example/public/components/step_three.tsx @@ -30,7 +30,7 @@ export const StepThree = (props: StepThreeProps) => { useEffect(() => { const subscription = guidedOnboardingApi - ?.isGuideStepActive$('search', 'search_experience') + ?.isGuideStepActive$('testGuide', 'step3') .subscribe((isStepActive) => { setIsTourStepOpen(isStepActive); }); @@ -53,9 +53,17 @@ export const StepThree = (props: StepThreeProps) => {

    +

    +

    +

    @@ -73,12 +81,12 @@ export const StepThree = (props: StepThreeProps) => { }} step={1} stepsTotal={1} - title="Step Build search experience" + title="Step 3" anchorPosition="rightUp" > { - await guidedOnboardingApi?.completeGuideStep('search', 'search_experience'); + await guidedOnboardingApi?.completeGuideStep('testGuide', 'step3'); }} > Complete step 3 diff --git a/examples/guided_onboarding_example/public/components/step_two.tsx b/examples/guided_onboarding_example/public/components/step_two.tsx index 07f4fd7e63e0c..89c0c37e46e4a 100644 --- a/examples/guided_onboarding_example/public/components/step_two.tsx +++ b/examples/guided_onboarding_example/public/components/step_two.tsx @@ -6,37 +6,17 @@ * Side Public License, v 1. */ -import React, { useEffect, useState } from 'react'; +import React from 'react'; -import { EuiButton, EuiSpacer, EuiText, EuiTitle, EuiTourStep } from '@elastic/eui'; +import { EuiText, EuiTitle } from '@elastic/eui'; -import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiPageContentHeader_Deprecated as EuiPageContentHeader, EuiPageContentBody_Deprecated as EuiPageContentBody, } from '@elastic/eui'; -interface StepTwoProps { - guidedOnboarding: GuidedOnboardingPluginStart; -} - -export const StepTwo = (props: StepTwoProps) => { - const { - guidedOnboarding: { guidedOnboardingApi }, - } = props; - - const [isTourStepOpen, setIsTourStepOpen] = useState(false); - - useEffect(() => { - const subscription = guidedOnboardingApi - ?.isGuideStepActive$('search', 'browse_docs') - .subscribe((isStepActive) => { - setIsTourStepOpen(isStepActive); - }); - return () => subscription?.unsubscribe(); - }, [guidedOnboardingApi]); - +export const StepTwo = () => { return ( <> @@ -54,36 +34,11 @@ export const StepTwo = (props: StepTwoProps) => {

    - - -

    Click this button to complete step 2.

    - - } - isStepOpen={isTourStepOpen} - minWidth={300} - onFinish={() => { - setIsTourStepOpen(false); - }} - step={1} - stepsTotal={1} - title="Step Browse documents" - anchorPosition="rightUp" - > - { - await guidedOnboardingApi?.completeGuideStep('search', 'browse_docs'); - }} - > - Complete step 2 - -
    ); diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json index 14e27632c33f4..8b49d43be942a 100644 --- a/nav-kibana-dev.docnav.json +++ b/nav-kibana-dev.docnav.json @@ -106,6 +106,10 @@ }, { "id": "kibDevDocsEmbeddables" + }, + { + "id": "kibCloudExperimentsPlugin", + "label": "A/B testing on Elastic Cloud" } ] }, diff --git a/package.json b/package.json index fbab0181a7f8e..bb7cd79238c33 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ }, "dependencies": { "@appland/sql-parser": "^1.5.1", - "@babel/runtime": "^7.19.0", + "@babel/runtime": "^7.19.4", "@dnd-kit/core": "^3.1.1", "@dnd-kit/sortable": "^4.0.0", "@dnd-kit/utilities": "^2.0.0", @@ -131,7 +131,6 @@ "@hapi/inert": "^6.0.4", "@hapi/wreck": "^17.1.0", "@kbn/ace": "link:bazel-bin/packages/kbn-ace", - "@kbn/adhoc-profiler": "link:bazel-bin/packages/kbn-adhoc-profiler", "@kbn/aiops-components": "link:bazel-bin/x-pack/packages/ml/aiops_components", "@kbn/aiops-utils": "link:bazel-bin/x-pack/packages/ml/aiops_utils", "@kbn/alerts": "link:bazel-bin/packages/kbn-alerts", @@ -242,6 +241,9 @@ "@kbn/core-lifecycle-browser": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser", "@kbn/core-lifecycle-browser-internal": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-internal", "@kbn/core-lifecycle-browser-mocks": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-mocks", + "@kbn/core-lifecycle-server": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-server", + "@kbn/core-lifecycle-server-internal": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-server-internal", + "@kbn/core-lifecycle-server-mocks": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-server-mocks", "@kbn/core-logging-server": "link:bazel-bin/packages/core/logging/core-logging-server", "@kbn/core-logging-server-internal": "link:bazel-bin/packages/core/logging/core-logging-server-internal", "@kbn/core-logging-server-mocks": "link:bazel-bin/packages/core/logging/core-logging-server-mocks", @@ -265,6 +267,9 @@ "@kbn/core-plugins-browser": "link:bazel-bin/packages/core/plugins/core-plugins-browser", "@kbn/core-plugins-browser-internal": "link:bazel-bin/packages/core/plugins/core-plugins-browser-internal", "@kbn/core-plugins-browser-mocks": "link:bazel-bin/packages/core/plugins/core-plugins-browser-mocks", + "@kbn/core-plugins-server": "link:bazel-bin/packages/core/plugins/core-plugins-server", + "@kbn/core-plugins-server-internal": "link:bazel-bin/packages/core/plugins/core-plugins-server-internal", + "@kbn/core-plugins-server-mocks": "link:bazel-bin/packages/core/plugins/core-plugins-server-mocks", "@kbn/core-preboot-server": "link:bazel-bin/packages/core/preboot/core-preboot-server", "@kbn/core-preboot-server-internal": "link:bazel-bin/packages/core/preboot/core-preboot-server-internal", "@kbn/core-preboot-server-mocks": "link:bazel-bin/packages/core/preboot/core-preboot-server-mocks", @@ -456,6 +461,7 @@ "bitmap-sdf": "^1.0.3", "blurhash": "^2.0.1", "brace": "0.11.1", + "brok": "^5.0.2", "byte-size": "^8.1.0", "canvg": "^3.0.9", "cbor-x": "^1.3.3", @@ -509,8 +515,8 @@ "he": "^1.2.0", "history": "^4.9.0", "hjson": "3.2.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^5.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", "i18n-iso-countries": "^4.3.1", "icalendar": "0.7.1", "immer": "^9.0.15", @@ -575,7 +581,6 @@ "peggy": "^1.2.0", "pluralize": "3.1.0", "polished": "^3.7.2", - "pprof": "^3.2.0", "pretty-ms": "6.0.0", "prop-types": "^15.8.1", "proxy-from-env": "1.0.0", @@ -594,7 +599,7 @@ "react-fast-compare": "^2.0.4", "react-focus-on": "^3.6.0", "react-grid-layout": "^1.3.4", - "react-hook-form": "^7.37.0", + "react-hook-form": "^7.38.0", "react-intl": "^2.8.0", "react-is": "^17.0.2", "react-markdown": "^6.0.3", @@ -667,32 +672,32 @@ "vinyl": "^2.2.0", "whatwg-fetch": "^3.0.0", "xml2js": "^0.4.22", - "xterm": "^4.18.0", + "xterm": "^5.0.0", "yauzl": "^2.10.0", "yazl": "^2.5.1" }, "devDependencies": { "@apidevtools/swagger-parser": "^10.0.3", "@babel/cli": "^7.19.3", - "@babel/core": "^7.19.3", + "@babel/core": "^7.19.6", "@babel/eslint-parser": "^7.19.1", "@babel/eslint-plugin": "^7.19.1", - "@babel/generator": "^7.19.3", + "@babel/generator": "^7.19.6", "@babel/helper-plugin-utils": "^7.19.0", - "@babel/parser": "^7.19.3", + "@babel/parser": "^7.19.6", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-export-namespace-from": "^7.18.9", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.18.9", + "@babel/plugin-proposal-object-rest-spread": "^7.19.4", "@babel/plugin-proposal-optional-chaining": "^7.18.9", "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-transform-runtime": "^7.19.1", - "@babel/preset-env": "^7.19.3", + "@babel/plugin-transform-runtime": "^7.19.6", + "@babel/preset-env": "^7.19.4", "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@babel/register": "^7.18.9", - "@babel/traverse": "^7.19.3", - "@babel/types": "^7.19.3", + "@babel/traverse": "^7.19.6", + "@babel/types": "^7.19.4", "@bazel/ibazel": "^0.16.2", "@bazel/typescript": "4.6.2", "@cypress/code-coverage": "^3.10.0", @@ -706,9 +711,9 @@ "@emotion/jest": "^11.10.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@istanbuljs/schema": "^0.1.2", - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/types": "^26", + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/types": "^27", "@kbn/ambient-storybook-types": "link:bazel-bin/packages/kbn-ambient-storybook-types", "@kbn/ambient-ui-types": "link:bazel-bin/packages/kbn-ambient-ui-types", "@kbn/apm-synthtrace": "link:bazel-bin/packages/kbn-apm-synthtrace", @@ -791,10 +796,10 @@ "@storybook/react-docgen-typescript-plugin": "^1.0.1", "@storybook/testing-react": "^1.3.0", "@storybook/theming": "^6.5.12", - "@testing-library/dom": "^8.12.0", - "@testing-library/jest-dom": "^5.16.3", + "@testing-library/dom": "^8.17.1", + "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.1.5", - "@testing-library/react-hooks": "^7.0.2", + "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.5.0", "@types/apidoc": "^0.22.3", "@types/archiver": "^5.3.1", @@ -844,10 +849,9 @@ "@types/history": "^4.7.9", "@types/hjson": "^2.4.2", "@types/http-proxy": "^1.17.4", - "@types/http-proxy-agent": "^2.0.2", "@types/inquirer": "^7.3.1", "@types/intl-relativeformat": "^2.1.0", - "@types/jest": "^26.0.22", + "@types/jest": "^27.4.1", "@types/jest-axe": "^3.5.3", "@types/joi": "^17.2.3", "@types/jquery": "^3.3.31", @@ -860,7 +864,6 @@ "@types/json5": "^0.0.30", "@types/jsonwebtoken": "^8.5.6", "@types/kbn__ace": "link:bazel-bin/packages/kbn-ace/npm_module_types", - "@types/kbn__adhoc-profiler": "link:bazel-bin/packages/kbn-adhoc-profiler/npm_module_types", "@types/kbn__aiops-components": "link:bazel-bin/x-pack/packages/ml/aiops_components/npm_module_types", "@types/kbn__aiops-utils": "link:bazel-bin/x-pack/packages/ml/aiops_utils/npm_module_types", "@types/kbn__alerts": "link:bazel-bin/packages/kbn-alerts/npm_module_types", @@ -982,6 +985,9 @@ "@types/kbn__core-lifecycle-browser": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser/npm_module_types", "@types/kbn__core-lifecycle-browser-internal": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-internal/npm_module_types", "@types/kbn__core-lifecycle-browser-mocks": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-browser-mocks/npm_module_types", + "@types/kbn__core-lifecycle-server": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-server/npm_module_types", + "@types/kbn__core-lifecycle-server-internal": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-server-internal/npm_module_types", + "@types/kbn__core-lifecycle-server-mocks": "link:bazel-bin/packages/core/lifecycle/core-lifecycle-server-mocks/npm_module_types", "@types/kbn__core-logging-server": "link:bazel-bin/packages/core/logging/core-logging-server/npm_module_types", "@types/kbn__core-logging-server-internal": "link:bazel-bin/packages/core/logging/core-logging-server-internal/npm_module_types", "@types/kbn__core-logging-server-mocks": "link:bazel-bin/packages/core/logging/core-logging-server-mocks/npm_module_types", @@ -1005,6 +1011,9 @@ "@types/kbn__core-plugins-browser": "link:bazel-bin/packages/core/plugins/core-plugins-browser/npm_module_types", "@types/kbn__core-plugins-browser-internal": "link:bazel-bin/packages/core/plugins/core-plugins-browser-internal/npm_module_types", "@types/kbn__core-plugins-browser-mocks": "link:bazel-bin/packages/core/plugins/core-plugins-browser-mocks/npm_module_types", + "@types/kbn__core-plugins-server": "link:bazel-bin/packages/core/plugins/core-plugins-server/npm_module_types", + "@types/kbn__core-plugins-server-internal": "link:bazel-bin/packages/core/plugins/core-plugins-server-internal/npm_module_types", + "@types/kbn__core-plugins-server-mocks": "link:bazel-bin/packages/core/plugins/core-plugins-server-mocks/npm_module_types", "@types/kbn__core-preboot-server": "link:bazel-bin/packages/core/preboot/core-preboot-server/npm_module_types", "@types/kbn__core-preboot-server-internal": "link:bazel-bin/packages/core/preboot/core-preboot-server-internal/npm_module_types", "@types/kbn__core-preboot-server-mocks": "link:bazel-bin/packages/core/preboot/core-preboot-server-mocks/npm_module_types", @@ -1299,7 +1308,7 @@ "argsplit": "^1.0.5", "autoprefixer": "^10.4.7", "axe-core": "^4.0.2", - "babel-jest": "^26.6.3", + "babel-jest": "^27.5.1", "babel-loader": "^8.2.5", "babel-plugin-add-module-exports": "^1.0.4", "babel-plugin-istanbul": "^6.1.1", @@ -1334,7 +1343,7 @@ "dpdm": "3.5.0", "ejs": "^3.1.8", "enzyme": "^3.11.0", - "enzyme-to-json": "^3.6.1", + "enzyme-to-json": "^3.6.2", "eslint": "^7.32.0", "eslint-config-prettier": "^8.5.0", "eslint-module-utils": "^2.6.2", @@ -1372,21 +1381,21 @@ "html-loader": "^1.3.2", "http-proxy": "^1.18.1", "is-path-inside": "^3.0.2", - "jest": "^26.6.3", + "jest": "^27.5.1", "jest-axe": "^5.0.0", - "jest-canvas-mock": "^2.3.1", - "jest-circus": "^26.6.3", - "jest-cli": "^26.6.3", - "jest-config": "^26", - "jest-diff": "^26.6.2", - "jest-environment-jsdom": "^26.6.2", - "jest-mock": "^26.6.2", + "jest-canvas-mock": "^2.4.0", + "jest-cli": "^27.5.1", + "jest-config": "^27.5.1", + "jest-diff": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-mock": "^27.5.1", "jest-raw-loader": "^1.0.1", - "jest-runtime": "^26", + "jest-runtime": "^27.5.1", "jest-silent-reporter": "^0.5.0", - "jest-snapshot": "^26.6.2", - "jest-specific-snapshot": "^4.0.0", - "jest-styled-components": "^7.0.3", + "jest-snapshot": "^27.5.1", + "jest-specific-snapshot": "^5.0.0", + "jest-styled-components": "7.0.3", "jsdom": "^16.4.0", "json-schema-typed": "^8.0.1", "json5": "^1.0.1", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 95c72f1cd9b9f..d14389555251f 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -105,6 +105,9 @@ filegroup( "//packages/core/lifecycle/core-lifecycle-browser:build", "//packages/core/lifecycle/core-lifecycle-browser-internal:build", "//packages/core/lifecycle/core-lifecycle-browser-mocks:build", + "//packages/core/lifecycle/core-lifecycle-server:build", + "//packages/core/lifecycle/core-lifecycle-server-internal:build", + "//packages/core/lifecycle/core-lifecycle-server-mocks:build", "//packages/core/logging/core-logging-server:build", "//packages/core/logging/core-logging-server-internal:build", "//packages/core/logging/core-logging-server-mocks:build", @@ -128,6 +131,9 @@ filegroup( "//packages/core/plugins/core-plugins-browser:build", "//packages/core/plugins/core-plugins-browser-internal:build", "//packages/core/plugins/core-plugins-browser-mocks:build", + "//packages/core/plugins/core-plugins-server:build", + "//packages/core/plugins/core-plugins-server-internal:build", + "//packages/core/plugins/core-plugins-server-mocks:build", "//packages/core/preboot/core-preboot-server:build", "//packages/core/preboot/core-preboot-server-internal:build", "//packages/core/preboot/core-preboot-server-mocks:build", @@ -181,7 +187,6 @@ filegroup( "//packages/home/sample_data_tab:build", "//packages/home/sample_data_types:build", "//packages/kbn-ace:build", - "//packages/kbn-adhoc-profiler:build", "//packages/kbn-alerts:build", "//packages/kbn-ambient-storybook-types:build", "//packages/kbn-ambient-ui-types:build", @@ -455,6 +460,9 @@ filegroup( "//packages/core/lifecycle/core-lifecycle-browser:build_types", "//packages/core/lifecycle/core-lifecycle-browser-internal:build_types", "//packages/core/lifecycle/core-lifecycle-browser-mocks:build_types", + "//packages/core/lifecycle/core-lifecycle-server:build_types", + "//packages/core/lifecycle/core-lifecycle-server-internal:build_types", + "//packages/core/lifecycle/core-lifecycle-server-mocks:build_types", "//packages/core/logging/core-logging-server:build_types", "//packages/core/logging/core-logging-server-internal:build_types", "//packages/core/logging/core-logging-server-mocks:build_types", @@ -478,6 +486,9 @@ filegroup( "//packages/core/plugins/core-plugins-browser:build_types", "//packages/core/plugins/core-plugins-browser-internal:build_types", "//packages/core/plugins/core-plugins-browser-mocks:build_types", + "//packages/core/plugins/core-plugins-server:build_types", + "//packages/core/plugins/core-plugins-server-internal:build_types", + "//packages/core/plugins/core-plugins-server-mocks:build_types", "//packages/core/preboot/core-preboot-server:build_types", "//packages/core/preboot/core-preboot-server-internal:build_types", "//packages/core/preboot/core-preboot-server-mocks:build_types", @@ -530,7 +541,6 @@ filegroup( "//packages/home/sample_data_card:build_types", "//packages/home/sample_data_tab:build_types", "//packages/kbn-ace:build_types", - "//packages/kbn-adhoc-profiler:build_types", "//packages/kbn-alerts:build_types", "//packages/kbn-analytics:build_types", "//packages/kbn-apm-config-loader:build_types", diff --git a/packages/analytics/client/src/analytics_client/analytics_client.test.ts b/packages/analytics/client/src/analytics_client/analytics_client.test.ts index a53489e312083..601f94aa1e243 100644 --- a/packages/analytics/client/src/analytics_client/analytics_client.test.ts +++ b/packages/analytics/client/src/analytics_client/analytics_client.test.ts @@ -21,7 +21,7 @@ describe('AnalyticsClient', () => { let logger: MockedLogger; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); logger = loggerMock.create(); analyticsClient = new AnalyticsClient({ logger, diff --git a/packages/analytics/shippers/README.md b/packages/analytics/shippers/README.md index 5ab85d08ff2ca..109bdd64700ab 100644 --- a/packages/analytics/shippers/README.md +++ b/packages/analytics/shippers/README.md @@ -3,5 +3,6 @@ This directory holds the implementation of the _built-in_ shippers provided by the Analytics client. At the moment, the shippers are: * [FullStory](./fullstory/README.md) +* [Gainsight](./gainsight/README.md) * [Elastic V3 (Browser shipper)](./elastic_v3/browser/README.md) * [Elastic V3 (Server-side shipper)](./elastic_v3/server/README.md) diff --git a/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.test.ts b/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.test.ts index fae3121372193..47728a99a511a 100644 --- a/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.test.ts +++ b/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.test.ts @@ -33,7 +33,7 @@ describe('ElasticV3BrowserShipper', () => { let fetchMock: jest.Mock; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); fetchMock = jest.fn().mockResolvedValue({ status: 200, diff --git a/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.ts b/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.ts index 03356f7428da7..091be6cb96e83 100644 --- a/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.ts +++ b/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.ts @@ -40,7 +40,7 @@ describe('ElasticV3ServerShipper', () => { const setLastBatchSent = (ms: number) => (shipper['lastBatchSent'] = ms); beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); shipper = new ElasticV3ServerShipper( { version: '1.2.3', channelName: 'test-channel', debug: true }, diff --git a/packages/analytics/shippers/gainsight/README.md b/packages/analytics/shippers/gainsight/README.md index 64899c52b554f..3baed8dcdf479 100644 --- a/packages/analytics/shippers/gainsight/README.md +++ b/packages/analytics/shippers/gainsight/README.md @@ -1,6 +1,6 @@ # @kbn/analytics-shippers-gainsight -gainsight implementation as a shipper for the `@kbn/analytics-client`. +Gainsight implementation as a shipper for the `@kbn/analytics-client`. ## How to use it diff --git a/packages/content-management/table_list/src/table_list_view.test.tsx b/packages/content-management/table_list/src/table_list_view.test.tsx index 8e5aff4ae0dbe..01b1a3b215ea1 100644 --- a/packages/content-management/table_list/src/table_list_view.test.tsx +++ b/packages/content-management/table_list/src/table_list_view.test.tsx @@ -49,7 +49,7 @@ const requiredProps: TableListViewProps = { describe('TableListView', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts index d3514cb80edf3..177a01393a3e2 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts @@ -12,6 +12,18 @@ import { injectedMetadataServiceMock } from '@kbn/core-injected-metadata-browser import { analyticsClientMock } from './analytics_service.test.mocks'; import { AnalyticsService } from './analytics_service'; +function findRegisteredContextProviderByName(contextProviderName: string) { + return analyticsClientMock.registerContextProvider.mock.calls.find( + ([{ name }]) => name === contextProviderName + )!; +} + +function findRegisteredEventTypeByName(eventTypeName: string) { + return analyticsClientMock.registerEventType.mock.calls.find( + ([{ eventType }]) => eventType === eventTypeName + )!; +} + describe('AnalyticsService', () => { let analyticsService: AnalyticsService; beforeEach(() => { @@ -19,34 +31,39 @@ describe('AnalyticsService', () => { analyticsService = new AnalyticsService(coreContextMock.create()); }); test('should register some context providers on creation', async () => { - expect(analyticsClientMock.registerContextProvider).toHaveBeenCalledTimes(3); - expect( - await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[0][0].context$) - ).toMatchInlineSnapshot(` - Object { - "branch": "branch", - "buildNum": 100, - "buildSha": "buildSha", - "isDev": true, - "isDistributable": false, - "version": "version", - } - `); + expect(analyticsClientMock.registerContextProvider).toHaveBeenCalledTimes(4); + expect(await firstValueFrom(findRegisteredContextProviderByName('build info')[0].context$)) + .toMatchInlineSnapshot(` + Object { + "branch": "branch", + "buildNum": 100, + "buildSha": "buildSha", + "isDev": true, + "isDistributable": false, + "version": "version", + } + `); expect( - await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[1][0].context$) + await firstValueFrom(findRegisteredContextProviderByName('session-id')[0].context$) ).toEqual({ session_id: expect.any(String) }); expect( - await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[2][0].context$) + await firstValueFrom(findRegisteredContextProviderByName('browser info')[0].context$) ).toEqual({ preferred_language: 'en-US', preferred_languages: ['en-US', 'en'], user_agent: expect.any(String), }); + expect( + await firstValueFrom(findRegisteredContextProviderByName('viewport_size')[0].context$) + ).toEqual({ + viewport_width: 1024, + viewport_height: 768, + }); }); test('should register the `performance_metric` and `click` event types on creation', () => { - expect(analyticsClientMock.registerEventType).toHaveBeenCalledTimes(2); - expect(analyticsClientMock.registerEventType.mock.calls[0]).toMatchInlineSnapshot(` + expect(analyticsClientMock.registerEventType).toHaveBeenCalledTimes(3); + expect(findRegisteredEventTypeByName('performance_metric')).toMatchInlineSnapshot(` Array [ Object { "eventType": "performance_metric", @@ -144,7 +161,7 @@ describe('AnalyticsService', () => { }, ] `); - expect(analyticsClientMock.registerEventType.mock.calls[1]).toMatchInlineSnapshot(` + expect(findRegisteredEventTypeByName('click')).toMatchInlineSnapshot(` Array [ Object { "eventType": "click", @@ -162,6 +179,27 @@ describe('AnalyticsService', () => { }, ] `); + expect(findRegisteredEventTypeByName('viewport_resize')).toMatchInlineSnapshot(` + Array [ + Object { + "eventType": "viewport_resize", + "schema": Object { + "viewport_height": Object { + "_meta": Object { + "description": "The value seen as the CSS viewport @media (height)", + }, + "type": "long", + }, + "viewport_width": Object { + "_meta": Object { + "description": "The value seen as the CSS viewport @media (width)", + }, + "type": "long", + }, + }, + }, + ] + `); }); test('setup should expose all the register APIs, reportEvent and opt-in', () => { @@ -181,7 +219,7 @@ describe('AnalyticsService', () => { const injectedMetadata = injectedMetadataServiceMock.createSetupContract(); analyticsService.setup({ injectedMetadata }); expect( - await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[3][0].context$) + await firstValueFrom(findRegisteredContextProviderByName('elasticsearch info')[0].context$) ).toMatchInlineSnapshot(`undefined`); }); @@ -194,14 +232,14 @@ describe('AnalyticsService', () => { }); analyticsService.setup({ injectedMetadata }); expect( - await firstValueFrom(analyticsClientMock.registerContextProvider.mock.calls[3][0].context$) + await firstValueFrom(findRegisteredContextProviderByName('elasticsearch info')[0].context$) ).toMatchInlineSnapshot(` - Object { - "cluster_name": "cluster_name", - "cluster_uuid": "cluster_uuid", - "cluster_version": "version", - } - `); + Object { + "cluster_name": "cluster_name", + "cluster_uuid": "cluster_uuid", + "cluster_version": "version", + } + `); }); test('setup should expose only the APIs report and opt-in', () => { diff --git a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts index 938b0b043bc29..0dcd49bd69fcc 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { of } from 'rxjs'; +import { of, Subscription } from 'rxjs'; import type { AnalyticsClient } from '@kbn/analytics-client'; import { createAnalytics } from '@kbn/analytics-client'; import { registerPerformanceMetricEventType } from '@kbn/ebt-tools'; @@ -16,6 +16,7 @@ import type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-ana import { trackClicks } from './track_clicks'; import { getSessionId } from './get_session_id'; import { createLogger } from './logger'; +import { trackViewportSize } from './track_viewport_size'; /** @internal */ export interface AnalyticsServiceSetupDeps { @@ -24,6 +25,7 @@ export interface AnalyticsServiceSetupDeps { export class AnalyticsService { private readonly analyticsClient: AnalyticsClient; + private readonly subscriptionsHandler = new Subscription(); constructor(core: CoreContext) { this.analyticsClient = createAnalytics({ @@ -41,7 +43,8 @@ export class AnalyticsService { // and can benefit other consumers of the client. this.registerSessionIdContext(); this.registerBrowserInfoAnalyticsContext(); - trackClicks(this.analyticsClient, core.env.mode.dev); + this.subscriptionsHandler.add(trackClicks(this.analyticsClient, core.env.mode.dev)); + this.subscriptionsHandler.add(trackViewportSize(this.analyticsClient)); } public setup({ injectedMetadata }: AnalyticsServiceSetupDeps): AnalyticsServiceSetup { @@ -67,6 +70,7 @@ export class AnalyticsService { } public stop() { + this.subscriptionsHandler.unsubscribe(); this.analyticsClient.shutdown(); } diff --git a/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts new file mode 100644 index 0000000000000..f99d6681e5ad8 --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.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 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 or the Server + * Side Public License, v 1. + */ + +import { firstValueFrom, take, type Subscription, toArray } from 'rxjs'; +import { analyticsClientMock } from './analytics_service.test.mocks'; +import { trackViewportSize } from './track_viewport_size'; + +describe('trackViewportSize', () => { + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + let subscription: Subscription | undefined; + + afterEach(() => { + subscription?.unsubscribe(); + jest.resetAllMocks(); + }); + + test('registers the analytics event type, the context provider, and a listener to the "resize" events', () => { + subscription = trackViewportSize(analyticsClientMock); + + expect(analyticsClientMock.registerContextProvider).toHaveBeenCalledTimes(1); + expect(analyticsClientMock.registerContextProvider).toHaveBeenCalledWith( + expect.objectContaining({ + name: 'viewport_size', + }) + ); + + expect(analyticsClientMock.registerEventType).toHaveBeenCalledTimes(1); + expect(analyticsClientMock.registerEventType).toHaveBeenCalledWith( + expect.objectContaining({ + eventType: 'viewport_resize', + }) + ); + expect(addEventListenerSpy).toHaveBeenCalledTimes(1); + expect(addEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function), undefined); + }); + + test('emits a context update before the resize occurs', async () => { + subscription = trackViewportSize(analyticsClientMock); + + const { context$ } = analyticsClientMock.registerContextProvider.mock.calls[0][0]; + await expect(firstValueFrom(context$.pipe(take(1)))).resolves.toStrictEqual({ + viewport_width: 1024, + viewport_height: 768, + }); + }); + + test('reports an analytics event when a resize event occurs', async () => { + subscription = trackViewportSize(analyticsClientMock); + + const { context$ } = analyticsClientMock.registerContextProvider.mock.calls[0][0]; + + // window.resizeTo(100, 100) wouldn't trigger the event in Jest, so we need to call the listener straight away. + // eslint-disable-next-line dot-notation + window['innerWidth'] = 100; + // eslint-disable-next-line dot-notation + window['innerHeight'] = 100; + + expect(addEventListenerSpy).toHaveBeenCalledTimes(1); + (addEventListenerSpy.mock.calls[0][1] as Function)(); + + await expect(firstValueFrom(context$.pipe(take(2), toArray()))).resolves.toStrictEqual([ + { viewport_width: 1024, viewport_height: 768 }, + { viewport_width: 100, viewport_height: 100 }, + ]); + + expect(analyticsClientMock.reportEvent).toHaveBeenCalledTimes(1); + expect(analyticsClientMock.reportEvent).toHaveBeenCalledWith('viewport_resize', { + viewport_width: 100, + viewport_height: 100, + }); + }); +}); diff --git a/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts new file mode 100644 index 0000000000000..17fab459f2e8f --- /dev/null +++ b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.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 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 or the Server + * Side Public License, v 1. + */ + +import { debounceTime, fromEvent, map, merge, of, shareReplay } from 'rxjs'; +import type { AnalyticsClient, RootSchema } from '@kbn/analytics-client'; + +export interface ViewportSize { + viewport_width: number; + viewport_height: number; +} + +const schema: RootSchema = { + viewport_width: { + type: 'long', + _meta: { description: 'The value seen as the CSS viewport @media (width)' }, + }, + viewport_height: { + type: 'long', + _meta: { description: 'The value seen as the CSS viewport @media (height)' }, + }, +}; + +/** + * Get the @media (width) and @media (height) in the format of {@link ViewportSize} + */ +function getViewportSize(): ViewportSize { + // Explanation of the math below: https://stackoverflow.com/questions/1248081/how-to-get-the-browser-viewport-dimensions + return { + viewport_width: Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0), + viewport_height: Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0), + }; +} + +/** + * Registers the event type "viewport_size" in the analytics client, and the context provider with the same name. + * Then it listens to all the "resize" events in the UI and reports their size as {@link ViewportSize} + * @param analytics + */ +export function trackViewportSize(analytics: AnalyticsClient) { + // window or document? + // According to MDN, it only emits on `window`: https://developer.mozilla.org/en-US/docs/Web/API/Window/resize_event + const resize$ = fromEvent(window, 'resize').pipe( + debounceTime(200), + map(() => getViewportSize()), + shareReplay(1) + ); + + analytics.registerEventType({ + eventType: 'viewport_resize', + schema, + }); + + analytics.registerContextProvider({ + name: 'viewport_size', + schema, + context$: merge( + of(getViewportSize()), // Emits an initial value so initial events' context is also populated + resize$ + ), + }); + + return resize$.subscribe((event) => analytics.reportEvent('viewport_resize', event)); +} 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 3c4b42cddfae7..f039fb7c24740 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 @@ -23,6 +23,7 @@ import type { ChromeBadge, ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension, + ChromeGlobalHelpExtensionMenuLink, ChromeHelpExtension, ChromeUserBanner, } from '@kbn/core-chrome-browser'; @@ -103,6 +104,9 @@ export class ChromeService { }: StartDeps): Promise { this.initVisibility(application); + const globalHelpExtensionMenuLinks$ = new BehaviorSubject( + [] + ); const helpExtension$ = new BehaviorSubject(undefined); const breadcrumbs$ = new BehaviorSubject([]); const breadcrumbsAppendExtension$ = new BehaviorSubject< @@ -213,6 +217,7 @@ export class ChromeService { customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))} kibanaDocLink={docLinks.links.kibana.guide} forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()} + globalHelpExtensionMenuLinks$={globalHelpExtensionMenuLinks$} helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))} helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))} homeHref={http.basePath.prepend('/app/home')} @@ -253,6 +258,17 @@ export class ChromeService { breadcrumbsAppendExtension$.next(breadcrumbsAppendExtension); }, + getGlobalHelpExtensionMenuLinks$: () => globalHelpExtensionMenuLinks$.asObservable(), + + registerGlobalHelpExtensionMenuLink: ( + globalHelpExtensionMenuLink: ChromeGlobalHelpExtensionMenuLink + ) => { + globalHelpExtensionMenuLinks$.next([ + ...globalHelpExtensionMenuLinks$.value, + globalHelpExtensionMenuLink, + ]); + }, + getHelpExtension$: () => helpExtension$.pipe(takeUntil(this.stop$)), setHelpExtension: (helpExtension?: ChromeHelpExtension) => { diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.test.tsx index 58ac26174f205..02fcce82d2c80 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.test.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.test.tsx @@ -33,6 +33,7 @@ function mockProps() { customNavLink$: new BehaviorSubject(undefined), recentlyAccessed$: new BehaviorSubject([]), forceAppSwitcherNavigation$: new BehaviorSubject(false), + globalHelpExtensionMenuLinks$: new BehaviorSubject([]), helpExtension$: new BehaviorSubject(undefined), helpSupportUrl$: new BehaviorSubject(''), navControlsLeft$: new BehaviorSubject([]), diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx index 5ff0989f16485..cfc79f2779a24 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header.tsx @@ -32,6 +32,7 @@ import type { ChromeRecentlyAccessedHistoryItem, ChromeBreadcrumbsAppendExtension, ChromeHelpExtension, + ChromeGlobalHelpExtensionMenuLink, ChromeUserBanner, } from '@kbn/core-chrome-browser'; import { LoadingIndicator } from '../loading_indicator'; @@ -60,6 +61,7 @@ export interface HeaderProps { navLinks$: Observable; recentlyAccessed$: Observable; forceAppSwitcherNavigation$: Observable; + globalHelpExtensionMenuLinks$: Observable; helpExtension$: Observable; helpSupportUrl$: Observable; navControlsLeft$: Observable; @@ -80,6 +82,7 @@ export function Header({ onIsLockedUpdate, homeHref, breadcrumbsAppendExtension$, + globalHelpExtensionMenuLinks$, ...observables }: HeaderProps) { const isVisible = useObservable(observables.isVisible$, false); @@ -145,6 +148,7 @@ export function Header({ , { + test('it only renders the default content', () => { + const application = applicationServiceMock.createInternalStartContract(); + const helpExtension$ = new BehaviorSubject(undefined); + const helpSupportUrl$ = new BehaviorSubject(''); + + const component = mountWithIntl( + + ); + + expect(component.find('EuiButtonEmpty').length).toBe(1); // only the toggle view on/off button + component.find('EuiButtonEmpty').simulate('click'); + + // 4 default links + the toggle button + expect(component.find('EuiButtonEmpty').length).toBe(5); + }); + + test('it renders the global custom content + the default content', () => { + const application = applicationServiceMock.createInternalStartContract(); + const helpExtension$ = new BehaviorSubject(undefined); + const helpSupportUrl$ = new BehaviorSubject(''); + + const component = mountWithIntl( + + ); + + expect(component.find('EuiButtonEmpty').length).toBe(1); // only the toggle view on/off button + component.find('EuiButtonEmpty').simulate('click'); + + // 2 custom global link + 4 default links + the toggle button + expect(component.find('EuiButtonEmpty').length).toBe(7); + + expect(component.find('[data-test-subj="my-test-custom-link"]').exists()).toBeTruthy(); + + // The first global component is the second button (first is the toggle button) + expect(component.find('EuiButtonEmpty').at(1).prop('data-test-subj')).toBe( + 'my-test-custom-link' + ); + }); +}); diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx index 4e797df335543..f938608091ba6 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_help_menu.tsx @@ -25,13 +25,17 @@ import { } from '@elastic/eui'; import type { InternalApplicationStart } from '@kbn/core-application-browser-internal'; -import type { ChromeHelpExtension } from '@kbn/core-chrome-browser'; +import type { + ChromeHelpExtension, + ChromeGlobalHelpExtensionMenuLink, +} from '@kbn/core-chrome-browser'; import { GITHUB_CREATE_ISSUE_LINK, KIBANA_FEEDBACK_LINK } from '../../constants'; import { HeaderExtension } from './header_extension'; import { isModifiedOrPrevented } from './nav_link'; interface Props { navigateToUrl: InternalApplicationStart['navigateToUrl']; + globalHelpExtensionMenuLinks$: Observable; helpExtension$: Observable; helpSupportUrl$: Observable; kibanaVersion: string; @@ -42,6 +46,7 @@ interface State { isOpen: boolean; helpExtension?: ChromeHelpExtension; helpSupportUrl: string; + globalHelpExtensionMenuLinks: ChromeGlobalHelpExtensionMenuLink[]; } export class HeaderHelpMenu extends Component { @@ -54,17 +59,20 @@ export class HeaderHelpMenu extends Component { isOpen: false, helpExtension: undefined, helpSupportUrl: '', + globalHelpExtensionMenuLinks: [], }; } public componentDidMount() { this.subscription = combineLatest( this.props.helpExtension$, - this.props.helpSupportUrl$ - ).subscribe(([helpExtension, helpSupportUrl]) => { + this.props.helpSupportUrl$, + this.props.globalHelpExtensionMenuLinks$ + ).subscribe(([helpExtension, helpSupportUrl, globalHelpExtensionMenuLinks]) => { this.setState({ helpExtension, helpSupportUrl, + globalHelpExtensionMenuLinks, }); }); } @@ -80,6 +88,7 @@ export class HeaderHelpMenu extends Component { const { kibanaVersion } = this.props; const defaultContent = this.renderDefaultContent(); + const globalCustomContent = this.renderGlobalCustomContent(); const customContent = this.renderCustomContent(); const button = ( @@ -126,8 +135,9 @@ export class HeaderHelpMenu extends Component {
    + {globalCustomContent} {defaultContent} - {defaultContent && customContent && } + {(defaultContent || customContent) && } {customContent}
    @@ -183,6 +193,22 @@ export class HeaderHelpMenu extends Component { ); } + private renderGlobalCustomContent() { + const { navigateToUrl } = this.props; + const { globalHelpExtensionMenuLinks } = this.state; + + return globalHelpExtensionMenuLinks + .sort((a, b) => b.priority - a.priority) + .map((link, index) => { + const { linkType, content: text, href, ...rest } = link; + return createCustomLink(index, text, true, { + href, + onClick: this.createOnClickHandler(href, navigateToUrl), + ...rest, + }); + }); + } + private renderCustomContent() { const { helpExtension } = this.state; if (!helpExtension) { 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 fc2a48f1329d4..2f5c4deb1f38d 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 @@ -50,6 +50,8 @@ const createStartContractMock = () => { setBreadcrumbs: jest.fn(), getBreadcrumbsAppendExtension$: jest.fn(), setBreadcrumbsAppendExtension: jest.fn(), + getGlobalHelpExtensionMenuLinks$: jest.fn(), + registerGlobalHelpExtensionMenuLink: jest.fn(), getHelpExtension$: jest.fn(), setHelpExtension: jest.fn(), setHelpSupportUrl: jest.fn(), @@ -66,6 +68,7 @@ const createStartContractMock = () => { startContract.getBreadcrumbs$.mockReturnValue(new BehaviorSubject([{} as ChromeBreadcrumb])); startContract.getBreadcrumbsAppendExtension$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getCustomNavLink$.mockReturnValue(new BehaviorSubject(undefined)); + startContract.getGlobalHelpExtensionMenuLinks$.mockReturnValue(new BehaviorSubject([])); startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false)); startContract.getBodyClasses$.mockReturnValue(new BehaviorSubject([])); diff --git a/packages/core/chrome/core-chrome-browser/index.ts b/packages/core/chrome/core-chrome-browser/index.ts index 16f0134afb7bb..3fbef34126a4a 100644 --- a/packages/core/chrome/core-chrome-browser/index.ts +++ b/packages/core/chrome/core-chrome-browser/index.ts @@ -23,6 +23,7 @@ export type { ChromeHelpExtensionMenuDocumentationLink, ChromeHelpExtensionMenuDiscussLink, ChromeHelpExtensionMenuCustomLink, + ChromeGlobalHelpExtensionMenuLink, ChromeDocTitle, ChromeStart, ChromeRecentlyAccessed, diff --git a/packages/core/chrome/core-chrome-browser/src/contracts.ts b/packages/core/chrome/core-chrome-browser/src/contracts.ts index e3a4f09c2cbb7..a81d9c3c6338f 100644 --- a/packages/core/chrome/core-chrome-browser/src/contracts.ts +++ b/packages/core/chrome/core-chrome-browser/src/contracts.ts @@ -14,6 +14,7 @@ import type { ChromeNavControls } from './nav_controls'; import type { ChromeHelpExtension } from './help_extension'; import type { ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension } from './breadcrumb'; import type { ChromeBadge, ChromeUserBanner } from './types'; +import { ChromeGlobalHelpExtensionMenuLink } from './help_extension'; /** * ChromeStart allows plugins to customize the global chrome header UI and @@ -106,7 +107,19 @@ export interface ChromeStart { setCustomNavLink(newCustomNavLink?: Partial): void; /** - * Get an observable of the current custom help conttent + * Get the list of the registered global help extension menu links + */ + getGlobalHelpExtensionMenuLinks$(): Observable; + + /** + * Append a global help extension menu link + */ + registerGlobalHelpExtensionMenuLink( + globalHelpExtensionMenuLink: ChromeGlobalHelpExtensionMenuLink + ): void; + + /** + * Get an observable of the current custom help content */ getHelpExtension$(): Observable; diff --git a/packages/core/chrome/core-chrome-browser/src/help_extension.ts b/packages/core/chrome/core-chrome-browser/src/help_extension.ts index 3acebd168f49a..f682a166ca29c 100644 --- a/packages/core/chrome/core-chrome-browser/src/help_extension.ts +++ b/packages/core/chrome/core-chrome-browser/src/help_extension.ts @@ -94,6 +94,14 @@ export interface ChromeHelpExtensionMenuCustomLink extends ChromeHelpExtensionLi content: React.ReactNode; } +/** @public */ +export interface ChromeGlobalHelpExtensionMenuLink extends ChromeHelpExtensionMenuCustomLink { + /** + * Highest priority items are listed at the top of the list of links. + */ + priority: number; +} + /** @public */ export type ChromeHelpExtensionMenuLink = | ChromeHelpExtensionMenuGitHubLink diff --git a/packages/core/chrome/core-chrome-browser/src/index.ts b/packages/core/chrome/core-chrome-browser/src/index.ts index 8414de3193c41..716af097fded7 100644 --- a/packages/core/chrome/core-chrome-browser/src/index.ts +++ b/packages/core/chrome/core-chrome-browser/src/index.ts @@ -18,6 +18,7 @@ export type { ChromeHelpExtensionMenuDiscussLink, ChromeHelpExtensionMenuDocumentationLink, ChromeHelpExtensionMenuGitHubLink, + ChromeGlobalHelpExtensionMenuLink, } from './help_extension'; export type { ChromeNavControls, ChromeNavControl } from './nav_controls'; export type { ChromeNavLinks, ChromeNavLink } from './nav_links'; diff --git a/packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts b/packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts index 881efe261d79f..82b4f04410a65 100644 --- a/packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts +++ b/packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts @@ -23,8 +23,10 @@ export const getApmConfig = (requestPath: string) => { return null; } + // Cleanup RUM unsupported attrbiutes from base apm config. + const { contextPropagationOnly, logUncaughtExceptions, ...restOfConfig } = baseConfig; const config: Record = { - ...baseConfig, + ...restOfConfig, pageLoadTransactionName: requestPath, }; diff --git a/packages/core/http/core-http-server-internal/BUILD.bazel b/packages/core/http/core-http-server-internal/BUILD.bazel index ab10546a3ddcc..214bb5833b7a9 100644 --- a/packages/core/http/core-http-server-internal/BUILD.bazel +++ b/packages/core/http/core-http-server-internal/BUILD.bazel @@ -44,6 +44,7 @@ RUNTIME_DEPS = [ "@npm//@hapi/cookie", "@npm//@hapi/inert", "@npm//elastic-apm-node", + "@npm//brok", "//packages/kbn-utils", "//packages/kbn-std", "//packages/kbn-config-schema", @@ -68,6 +69,7 @@ TYPES_DEPS = [ "@npm//moment", "@npm//@elastic/numeral", "@npm//lodash", + "@npm//brok", "@npm//@hapi/hapi", "@npm//@hapi/boom", "@npm//@hapi/cookie", diff --git a/packages/core/http/core-http-server-internal/src/__snapshots__/http_config.test.ts.snap b/packages/core/http/core-http-server-internal/src/__snapshots__/http_config.test.ts.snap index 65ac08f6ce5f7..bb81f7b3bc924 100644 --- a/packages/core/http/core-http-server-internal/src/__snapshots__/http_config.test.ts.snap +++ b/packages/core/http/core-http-server-internal/src/__snapshots__/http_config.test.ts.snap @@ -42,6 +42,10 @@ exports[`has defaults for config 1`] = ` Object { "autoListen": true, "compression": Object { + "brotli": Object { + "enabled": false, + "quality": 3, + }, "enabled": true, }, "cors": Object { diff --git a/packages/core/http/core-http-server-internal/src/http_config.test.ts b/packages/core/http/core-http-server-internal/src/http_config.test.ts index 26a42f27a794b..ec9fc41ed02fd 100644 --- a/packages/core/http/core-http-server-internal/src/http_config.test.ts +++ b/packages/core/http/core-http-server-internal/src/http_config.test.ts @@ -390,6 +390,33 @@ describe('with compression', () => { }); }); +describe('compression.brotli', () => { + describe('enabled', () => { + it('defaults to `false`', () => { + expect(config.schema.validate({}).compression.brotli.enabled).toEqual(false); + }); + }); + describe('quality', () => { + it('defaults to `3`', () => { + expect(config.schema.validate({}).compression.brotli.quality).toEqual(3); + }); + it('does not accepts value superior to `11`', () => { + expect(() => + config.schema.validate({ compression: { brotli: { quality: 12 } } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[compression.brotli.quality]: Value must be equal to or lower than [11]."` + ); + }); + it('does not accepts value inferior to `0`', () => { + expect(() => + config.schema.validate({ compression: { brotli: { quality: -1 } } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[compression.brotli.quality]: Value must be equal to or greater than [0]."` + ); + }); + }); +}); + describe('cors', () => { describe('allowOrigin', () => { it('list cannot be empty', () => { diff --git a/packages/core/http/core-http-server-internal/src/http_config.ts b/packages/core/http/core-http-server-internal/src/http_config.ts index 9cb636156c5e8..1fae2568edffd 100644 --- a/packages/core/http/core-http-server-internal/src/http_config.ts +++ b/packages/core/http/core-http-server-internal/src/http_config.ts @@ -112,6 +112,10 @@ const configSchema = schema.object( }), compression: schema.object({ enabled: schema.boolean({ defaultValue: true }), + brotli: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + quality: schema.number({ defaultValue: 3, min: 0, max: 11 }), + }), referrerWhitelist: schema.maybe( schema.arrayOf( schema.string({ @@ -209,7 +213,11 @@ export class HttpConfig implements IHttpConfig { public publicBaseUrl?: string; public rewriteBasePath: boolean; public ssl: SslConfig; - public compression: { enabled: boolean; referrerWhitelist?: string[] }; + public compression: { + enabled: boolean; + referrerWhitelist?: string[]; + brotli: { enabled: boolean; quality: number }; + }; public csp: ICspConfig; public externalUrl: IExternalUrlConfig; public xsrf: { disableProtection: boolean; allowlist: string[] }; diff --git a/packages/core/http/core-http-server-internal/src/http_server.test.ts b/packages/core/http/core-http-server-internal/src/http_server.test.ts index 82debfa44c2cb..92fa63c502558 100644 --- a/packages/core/http/core-http-server-internal/src/http_server.test.ts +++ b/packages/core/http/core-http-server-internal/src/http_server.test.ts @@ -60,7 +60,7 @@ beforeEach(() => { maxPayload: new ByteSizeValue(1024), port: 10002, ssl: { enabled: false }, - compression: { enabled: true }, + compression: { enabled: true, brotli: { enabled: false, quality: 3 } }, requestId: { allowFromAnyIp: true, ipAllowlist: [], @@ -865,7 +865,7 @@ describe('conditional compression', () => { test('with `compression.enabled: false`', async () => { const listener = await setupServer({ ...config, - compression: { enabled: false }, + compression: { enabled: false, brotli: { enabled: false, quality: 3 } }, }); const response = await supertest(listener).get('/').set('accept-encoding', 'gzip'); @@ -873,12 +873,38 @@ describe('conditional compression', () => { expect(response.header).not.toHaveProperty('content-encoding'); }); + test('with `compression.brotli.enabled: false`', async () => { + const listener = await setupServer({ + ...config, + compression: { enabled: true, brotli: { enabled: false, quality: 3 } }, + }); + + const response = await supertest(listener).get('/').set('accept-encoding', 'br'); + + expect(response.header).not.toHaveProperty('content-encoding', 'br'); + }); + + test('with `compression.brotli.enabled: true`', async () => { + const listener = await setupServer({ + ...config, + compression: { enabled: true, brotli: { enabled: true, quality: 3 } }, + }); + + const response = await supertest(listener).get('/').set('accept-encoding', 'br'); + + expect(response.header).toHaveProperty('content-encoding', 'br'); + }); + describe('with defined `compression.referrerWhitelist`', () => { let listener: Server; beforeEach(async () => { listener = await setupServer({ ...config, - compression: { enabled: true, referrerWhitelist: ['foo'] }, + compression: { + enabled: true, + referrerWhitelist: ['foo'], + brotli: { enabled: false, quality: 3 }, + }, }); }); diff --git a/packages/core/http/core-http-server-internal/src/http_server.ts b/packages/core/http/core-http-server-internal/src/http_server.ts index 766fa131349e1..4e4bf17d7a17a 100644 --- a/packages/core/http/core-http-server-internal/src/http_server.ts +++ b/packages/core/http/core-http-server-internal/src/http_server.ts @@ -21,6 +21,8 @@ import type { Duration } from 'moment'; import { firstValueFrom, Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import apm from 'elastic-apm-node'; +// @ts-expect-error no type definition +import Brok from 'brok'; import type { Logger, LoggerFactory } from '@kbn/logging'; import type { InternalExecutionContextSetup } from '@kbn/core-execution-context-server-internal'; import { isSafeMethod } from '@kbn/core-http-router-server-internal'; @@ -147,9 +149,17 @@ export class HttpServer { ): Promise { const serverOptions = getServerOptions(config); const listenerOptions = getListenerOptions(config); + this.config = config; this.server = createServer(serverOptions, listenerOptions); await this.server.register([HapiStaticFiles]); - this.config = config; + if (config.compression.brotli.enabled) { + await this.server.register({ + plugin: Brok, + options: { + compress: { quality: config.compression.brotli.quality }, + }, + }); + } // It's important to have setupRequestStateAssignment call the very first, otherwise context passing will be broken. // That's the only reason why context initialization exists in this method. diff --git a/packages/core/http/core-http-server-mocks/src/test_utils.ts b/packages/core/http/core-http-server-mocks/src/test_utils.ts index 2b9658693dce7..bb260ae23c908 100644 --- a/packages/core/http/core-http-server-mocks/src/test_utils.ts +++ b/packages/core/http/core-http-server-mocks/src/test_utils.ts @@ -35,7 +35,7 @@ const createConfigService = () => { cors: { enabled: false, }, - compression: { enabled: true }, + compression: { enabled: true, brotli: { enabled: false } }, xsrf: { disableProtection: true, allowlist: [], diff --git a/packages/core/lifecycle/core-lifecycle-server-internal/BUILD.bazel b/packages/core/lifecycle/core-lifecycle-server-internal/BUILD.bazel new file mode 100644 index 0000000000000..f09460293560f --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-internal/BUILD.bazel @@ -0,0 +1,125 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-lifecycle-server-internal" +PKG_REQUIRE_NAME = "@kbn/core-lifecycle-server-internal" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/core/logging/core-logging-server-internal:npm_module_types", + "//packages/core/analytics/core-analytics-server:npm_module_types", + "//packages/core/preboot/core-preboot-server-internal:npm_module_types", + "//packages/core/http/core-http-context-server-internal:npm_module_types", + "//packages/core/http/core-http-server-internal:npm_module_types", + "//packages/core/elasticsearch/core-elasticsearch-server-internal:npm_module_types", + "//packages/core/ui-settings/core-ui-settings-server-internal:npm_module_types", + "//packages/core/http/core-http-resources-server-internal:npm_module_types", + "//packages/core/capabilities/core-capabilities-server:npm_module_types", + "//packages/core/doc-links/core-doc-links-server:npm_module_types", + "//packages/core/i18n/core-i18n-server:npm_module_types", + "//packages/core/environment/core-environment-server-internal:npm_module_types", + "//packages/core/execution-context/core-execution-context-server-internal:npm_module_types", + "//packages/core/deprecations/core-deprecations-server-internal:npm_module_types", + "//packages/core/metrics/core-metrics-server-internal:npm_module_types", + "//packages/core/rendering/core-rendering-server-internal:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-server-internal:npm_module_types", + "//packages/core/status/core-status-server-internal:npm_module_types", + "//packages/core/usage-data/core-usage-data-base-server-internal:npm_module_types", + "//packages/core/usage-data/core-usage-data-server:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/lifecycle/core-lifecycle-server-internal/README.md b/packages/core/lifecycle/core-lifecycle-server-internal/README.md new file mode 100644 index 0000000000000..b2eecd8ad21ad --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-internal/README.md @@ -0,0 +1,7 @@ +# @kbn/core-lifecycle-server-internal + + +This package contains the internal types for core's server-side lifecycle contracts: +- `InternalCorePreboot` +- `InternalCoreSetup` +- `InternalCoreStart` diff --git a/packages/kbn-adhoc-profiler/require_pprof.ts b/packages/core/lifecycle/core-lifecycle-server-internal/index.ts similarity index 69% rename from packages/kbn-adhoc-profiler/require_pprof.ts rename to packages/core/lifecycle/core-lifecycle-server-internal/index.ts index beba96c2a4793..6c3a41be1b49d 100644 --- a/packages/kbn-adhoc-profiler/require_pprof.ts +++ b/packages/core/lifecycle/core-lifecycle-server-internal/index.ts @@ -6,9 +6,4 @@ * Side Public License, v 1. */ -import { PProf } from './types'; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const pprof = require('pprof') as PProf; - -export { pprof }; +export type { InternalCorePreboot, InternalCoreSetup, InternalCoreStart } from './src'; diff --git a/packages/kbn-adhoc-profiler/types.ts b/packages/core/lifecycle/core-lifecycle-server-internal/jest.config.js similarity index 67% rename from packages/kbn-adhoc-profiler/types.ts rename to packages/core/lifecycle/core-lifecycle-server-internal/jest.config.js index 76ef307b75de4..039817196b1fe 100644 --- a/packages/kbn-adhoc-profiler/types.ts +++ b/packages/core/lifecycle/core-lifecycle-server-internal/jest.config.js @@ -5,9 +5,9 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type pprofRuntime from 'pprof'; -type PProf = typeof pprofRuntime; -type Profile = ReturnType>; - -export type { PProf, Profile }; +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['/packages/core/lifecycle/core-lifecycle-server-internal'], +}; diff --git a/packages/core/lifecycle/core-lifecycle-server-internal/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-server-internal/kibana.jsonc new file mode 100644 index 0000000000000..7f8fa2fc8f6ad --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-internal/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-lifecycle-server-internal", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/lifecycle/core-lifecycle-server-internal/package.json b/packages/core/lifecycle/core-lifecycle-server-internal/package.json new file mode 100644 index 0000000000000..9b0c909b58e0e --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-internal/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-lifecycle-server-internal", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/lifecycle/core-lifecycle-server-internal/src/index.ts b/packages/core/lifecycle/core-lifecycle-server-internal/src/index.ts new file mode 100644 index 0000000000000..617706c7d53f9 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-internal/src/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 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 or the Server + * Side Public License, v 1. + */ + +export type { InternalCorePreboot } from './internal_core_preboot'; +export type { InternalCoreSetup } from './internal_core_setup'; +export type { InternalCoreStart } from './internal_core_start'; diff --git a/packages/core/lifecycle/core-lifecycle-server-internal/src/internal_core_preboot.ts b/packages/core/lifecycle/core-lifecycle-server-internal/src/internal_core_preboot.ts new file mode 100644 index 0000000000000..18f4c605c1b16 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-internal/src/internal_core_preboot.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 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 or the Server + * Side Public License, v 1. + */ +import type { InternalLoggingServicePreboot } from '@kbn/core-logging-server-internal'; +import type { AnalyticsServicePreboot } from '@kbn/core-analytics-server'; +import type { InternalPrebootServicePreboot } from '@kbn/core-preboot-server-internal'; +import type { InternalContextPreboot } from '@kbn/core-http-context-server-internal'; +import type { InternalHttpServicePreboot } from '@kbn/core-http-server-internal'; +import type { InternalElasticsearchServicePreboot } from '@kbn/core-elasticsearch-server-internal'; +import type { InternalUiSettingsServicePreboot } from '@kbn/core-ui-settings-server-internal'; +import type { InternalHttpResourcesPreboot } from '@kbn/core-http-resources-server-internal'; + +/** @internal */ +export interface InternalCorePreboot { + analytics: AnalyticsServicePreboot; + context: InternalContextPreboot; + http: InternalHttpServicePreboot; + elasticsearch: InternalElasticsearchServicePreboot; + uiSettings: InternalUiSettingsServicePreboot; + httpResources: InternalHttpResourcesPreboot; + logging: InternalLoggingServicePreboot; + preboot: InternalPrebootServicePreboot; +} diff --git a/packages/core/lifecycle/core-lifecycle-server-internal/src/internal_core_setup.ts b/packages/core/lifecycle/core-lifecycle-server-internal/src/internal_core_setup.ts new file mode 100644 index 0000000000000..5e706cd5f5b0c --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-internal/src/internal_core_setup.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 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 or the Server + * Side Public License, v 1. + */ + +import type { AnalyticsServiceSetup } from '@kbn/core-analytics-server'; +import type { CapabilitiesSetup } from '@kbn/core-capabilities-server'; +import type { DocLinksServiceSetup } from '@kbn/core-doc-links-server'; +import type { I18nServiceSetup } from '@kbn/core-i18n-server'; +import type { InternalElasticsearchServiceSetup } from '@kbn/core-elasticsearch-server-internal'; +import type { InternalEnvironmentServiceSetup } from '@kbn/core-environment-server-internal'; +import type { InternalExecutionContextSetup } from '@kbn/core-execution-context-server-internal'; +import type { InternalContextSetup } from '@kbn/core-http-context-server-internal'; +import type { InternalDeprecationsServiceSetup } from '@kbn/core-deprecations-server-internal'; +import type { InternalHttpResourcesSetup } from '@kbn/core-http-resources-server-internal'; +import type { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; +import type { InternalLoggingServiceSetup } from '@kbn/core-logging-server-internal'; +import type { InternalMetricsServiceSetup } from '@kbn/core-metrics-server-internal'; +import type { InternalRenderingServiceSetup } from '@kbn/core-rendering-server-internal'; +import type { InternalSavedObjectsServiceSetup } from '@kbn/core-saved-objects-server-internal'; +import type { InternalStatusServiceSetup } from '@kbn/core-status-server-internal'; +import type { InternalUiSettingsServiceSetup } from '@kbn/core-ui-settings-server-internal'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; + +/** @internal */ +export interface InternalCoreSetup { + analytics: AnalyticsServiceSetup; + capabilities: CapabilitiesSetup; + context: InternalContextSetup; + docLinks: DocLinksServiceSetup; + http: InternalHttpServiceSetup; + elasticsearch: InternalElasticsearchServiceSetup; + executionContext: InternalExecutionContextSetup; + i18n: I18nServiceSetup; + savedObjects: InternalSavedObjectsServiceSetup; + status: InternalStatusServiceSetup; + uiSettings: InternalUiSettingsServiceSetup; + environment: InternalEnvironmentServiceSetup; + rendering: InternalRenderingServiceSetup; + httpResources: InternalHttpResourcesSetup; + logging: InternalLoggingServiceSetup; + metrics: InternalMetricsServiceSetup; + deprecations: InternalDeprecationsServiceSetup; + coreUsageData: InternalCoreUsageDataSetup; +} diff --git a/packages/core/lifecycle/core-lifecycle-server-internal/src/internal_core_start.ts b/packages/core/lifecycle/core-lifecycle-server-internal/src/internal_core_start.ts new file mode 100644 index 0000000000000..e4d777d137b70 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-internal/src/internal_core_start.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 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 or the Server + * Side Public License, v 1. + */ + +import type { AnalyticsServiceStart } from '@kbn/core-analytics-server'; +import type { CapabilitiesStart } from '@kbn/core-capabilities-server'; +import type { InternalDeprecationsServiceStart } from '@kbn/core-deprecations-server-internal'; +import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import type { InternalElasticsearchServiceStart } from '@kbn/core-elasticsearch-server-internal'; +import type { InternalExecutionContextStart } from '@kbn/core-execution-context-server-internal'; +import type { InternalHttpServiceStart } from '@kbn/core-http-server-internal'; +import type { InternalMetricsServiceStart } from '@kbn/core-metrics-server-internal'; +import type { InternalSavedObjectsServiceStart } from '@kbn/core-saved-objects-server-internal'; +import type { InternalUiSettingsServiceStart } from '@kbn/core-ui-settings-server-internal'; +import type { CoreUsageDataStart } from '@kbn/core-usage-data-server'; + +/** + * @internal + */ +export interface InternalCoreStart { + analytics: AnalyticsServiceStart; + capabilities: CapabilitiesStart; + elasticsearch: InternalElasticsearchServiceStart; + docLinks: DocLinksServiceStart; + http: InternalHttpServiceStart; + metrics: InternalMetricsServiceStart; + savedObjects: InternalSavedObjectsServiceStart; + uiSettings: InternalUiSettingsServiceStart; + coreUsageData: CoreUsageDataStart; + executionContext: InternalExecutionContextStart; + deprecations: InternalDeprecationsServiceStart; +} diff --git a/packages/kbn-adhoc-profiler/tsconfig.json b/packages/core/lifecycle/core-lifecycle-server-internal/tsconfig.json similarity index 78% rename from packages/kbn-adhoc-profiler/tsconfig.json rename to packages/core/lifecycle/core-lifecycle-server-internal/tsconfig.json index bf5ec76a5e1fb..71bb40fe57f3f 100644 --- a/packages/kbn-adhoc-profiler/tsconfig.json +++ b/packages/core/lifecycle/core-lifecycle-server-internal/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.bazel.json", + "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, "declarationMap": true, @@ -8,8 +8,7 @@ "stripInternal": false, "types": [ "jest", - "node", - "long" + "node" ] }, "include": [ diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/BUILD.bazel b/packages/core/lifecycle/core-lifecycle-server-mocks/BUILD.bazel new file mode 100644 index 0000000000000..95f299b0062cd --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/BUILD.bazel @@ -0,0 +1,143 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-lifecycle-server-mocks" +PKG_REQUIRE_NAME = "@kbn/core-lifecycle-server-mocks" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "//packages/core/analytics/core-analytics-server-mocks", + "//packages/core/capabilities/core-capabilities-server-mocks", + "//packages/core/doc-links/core-doc-links-server-mocks", + "//packages/core/deprecations/core-deprecations-server-mocks", + "//packages/core/elasticsearch/core-elasticsearch-server-mocks", + "//packages/core/environment/core-environment-server-mocks", + "//packages/core/execution-context/core-execution-context-server-mocks", + "//packages/core/http/core-http-context-server-mocks", + "//packages/core/http/core-http-server-mocks", + "//packages/core/http/core-http-resources-server-mocks", + "//packages/core/i18n/core-i18n-server-mocks", + "//packages/core/lifecycle/core-lifecycle-server", + "//packages/core/metrics/core-metrics-server-mocks", + "//packages/core/preboot/core-preboot-server-mocks", + "//packages/core/rendering/core-rendering-server-mocks", + "//packages/core/saved-objects/core-saved-objects-server-mocks", + "//packages/core/status/core-status-server-mocks", + "//packages/core/ui-settings/core-ui-settings-server-mocks", + "//packages/core/usage-data/core-usage-data-server-mocks", + +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/kbn-utility-types-jest:npm_module_types", + "//packages/core/analytics/core-analytics-server-mocks:npm_module_types", + "//packages/core/capabilities/core-capabilities-server-mocks:npm_module_types", + "//packages/core/doc-links/core-doc-links-server-mocks:npm_module_types", + "//packages/core/deprecations/core-deprecations-server-mocks:npm_module_types", + "//packages/core/elasticsearch/core-elasticsearch-server-mocks:npm_module_types", + "//packages/core/environment/core-environment-server-mocks:npm_module_types", + "//packages/core/execution-context/core-execution-context-server-mocks:npm_module_types", + "//packages/core/http/core-http-context-server-mocks:npm_module_types", + "//packages/core/http/core-http-server-mocks:npm_module_types", + "//packages/core/http/core-http-resources-server-mocks:npm_module_types", + "//packages/core/i18n/core-i18n-server-mocks:npm_module_types", + "//packages/core/lifecycle/core-lifecycle-server:npm_module_types", + "//packages/core/metrics/core-metrics-server-mocks:npm_module_types", + "//packages/core/preboot/core-preboot-server-mocks:npm_module_types", + "//packages/core/rendering/core-rendering-server-mocks:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-server-mocks:npm_module_types", + "//packages/core/status/core-status-server-mocks:npm_module_types", + "//packages/core/ui-settings/core-ui-settings-server-mocks:npm_module_types", + "//packages/core/usage-data/core-usage-data-server-mocks:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/README.md b/packages/core/lifecycle/core-lifecycle-server-mocks/README.md new file mode 100644 index 0000000000000..6603c268f3d94 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/README.md @@ -0,0 +1,5 @@ +# @kbn/core-lifecycle-server-mocks + +This package contains the mocks for core's server-side lifecycle contracts: +- `coreLifecycleMock` +- `coreInternalLifecycleMock` diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/index.ts b/packages/core/lifecycle/core-lifecycle-server-mocks/index.ts new file mode 100644 index 0000000000000..e17f4db9de973 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/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 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 or the Server + * Side Public License, v 1. + */ + +export { coreLifecycleMock, coreInternalLifecycleMock } from './src'; diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/jest.config.js b/packages/core/lifecycle/core-lifecycle-server-mocks/jest.config.js new file mode 100644 index 0000000000000..3db7ab516cd56 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/jest.config.js @@ -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 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 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['/packages/core/lifecycle/core-lifecycle-server-mocks'], +}; diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-server-mocks/kibana.jsonc new file mode 100644 index 0000000000000..ea9bbadfd57e9 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-lifecycle-server-mocks", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/package.json b/packages/core/lifecycle/core-lifecycle-server-mocks/package.json new file mode 100644 index 0000000000000..ce6bae6105a29 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-lifecycle-server-mocks", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_preboot.mock.ts b/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_preboot.mock.ts new file mode 100644 index 0000000000000..43446f824faed --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_preboot.mock.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 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 or the Server + * Side Public License, v 1. + */ +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { analyticsServiceMock } from '@kbn/core-analytics-server-mocks'; +import { httpServiceMock } from '@kbn/core-http-server-mocks'; +import { prebootServiceMock } from '@kbn/core-preboot-server-mocks'; +import type { MockedKeys } from '@kbn/utility-types-jest'; +import type { CorePreboot } from '@kbn/core-lifecycle-server'; + +type CorePrebootMockType = MockedKeys & { + elasticsearch: ReturnType; +}; + +export function createCorePrebootMock() { + const mock: CorePrebootMockType = { + analytics: analyticsServiceMock.createAnalyticsServicePreboot(), + elasticsearch: elasticsearchServiceMock.createPreboot(), + http: httpServiceMock.createPrebootContract() as CorePrebootMockType['http'], + preboot: prebootServiceMock.createPrebootContract(), + }; + + return mock; +} diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_setup.mock.ts b/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_setup.mock.ts new file mode 100644 index 0000000000000..0c4e25e846cab --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_setup.mock.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 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 or the Server + * Side Public License, v 1. + */ + +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; +import { httpServiceMock } from '@kbn/core-http-server-mocks'; +import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; +import type { CoreSetup, StartServicesAccessor } from '@kbn/core-lifecycle-server'; +import type { MockedKeys } from '@kbn/utility-types-jest'; +import { analyticsServiceMock } from '@kbn/core-analytics-server-mocks'; +import { capabilitiesServiceMock } from '@kbn/core-capabilities-server-mocks'; +import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; +import { i18nServiceMock } from '@kbn/core-i18n-server-mocks'; +import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; +import { statusServiceMock } from '@kbn/core-status-server-mocks'; +import { loggingServiceMock } from '@kbn/core-logging-server-mocks'; +import { metricsServiceMock } from '@kbn/core-metrics-server-mocks'; +import { deprecationsServiceMock } from '@kbn/core-deprecations-server-mocks'; +import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks'; +import { coreUsageDataServiceMock } from '@kbn/core-usage-data-server-mocks'; +import { createCoreStartMock } from './core_start.mock'; + +type CoreSetupMockType = MockedKeys & { + elasticsearch: ReturnType; + getStartServices: jest.MockedFunction>; +}; + +export function createCoreSetupMock({ + pluginStartDeps = {}, + pluginStartContract, +}: { + pluginStartDeps?: object; + pluginStartContract?: any; +} = {}) { + const httpMock: jest.Mocked = { + ...httpServiceMock.createSetupContract(), + resources: httpResourcesMock.createRegistrar(), + }; + + const uiSettingsMock = { + register: uiSettingsServiceMock.createSetupContract().register, + }; + + const mock: CoreSetupMockType = { + analytics: analyticsServiceMock.createAnalyticsServiceSetup(), + capabilities: capabilitiesServiceMock.createSetupContract(), + docLinks: docLinksServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), + http: httpMock, + i18n: i18nServiceMock.createSetupContract(), + savedObjects: savedObjectsServiceMock.createInternalSetupContract(), + status: statusServiceMock.createSetupContract(), + uiSettings: uiSettingsMock, + logging: loggingServiceMock.createSetupContract(), + metrics: metricsServiceMock.createSetupContract(), + deprecations: deprecationsServiceMock.createSetupContract(), + executionContext: executionContextServiceMock.createInternalSetupContract(), + coreUsageData: { + registerUsageCounter: coreUsageDataServiceMock.createSetupContract().registerUsageCounter, + }, + getStartServices: jest + .fn, object, any]>, []>() + .mockResolvedValue([createCoreStartMock(), pluginStartDeps, pluginStartContract]), + }; + + return mock; +} diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_start.mock.ts b/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_start.mock.ts new file mode 100644 index 0000000000000..763a9c403eb39 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/src/core_start.mock.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 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 or the Server + * Side Public License, v 1. + */ + +import { analyticsServiceMock } from '@kbn/core-analytics-server-mocks'; +import { capabilitiesServiceMock } from '@kbn/core-capabilities-server-mocks'; +import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks'; +import { httpServiceMock } from '@kbn/core-http-server-mocks'; +import type { CoreStart } from '@kbn/core-lifecycle-server'; +import { metricsServiceMock } from '@kbn/core-metrics-server-mocks'; +import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; +import { coreUsageDataServiceMock } from '@kbn/core-usage-data-server-mocks'; +import type { MockedKeys } from '@kbn/utility-types-jest'; + +export function createCoreStartMock() { + const mock: MockedKeys = { + analytics: analyticsServiceMock.createAnalyticsServiceStart(), + capabilities: capabilitiesServiceMock.createStartContract(), + docLinks: docLinksServiceMock.createStartContract(), + elasticsearch: elasticsearchServiceMock.createStart(), + http: httpServiceMock.createStartContract(), + metrics: metricsServiceMock.createStartContract(), + savedObjects: savedObjectsServiceMock.createStartContract(), + uiSettings: uiSettingsServiceMock.createStartContract(), + coreUsageData: coreUsageDataServiceMock.createStartContract(), + executionContext: executionContextServiceMock.createInternalStartContract(), + }; + + return mock; +} diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/src/index.ts b/packages/core/lifecycle/core-lifecycle-server-mocks/src/index.ts new file mode 100644 index 0000000000000..66f9c1d818351 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/src/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 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 or the Server + * Side Public License, v 1. + */ + +import { createCorePrebootMock } from './core_preboot.mock'; +import { createCoreSetupMock } from './core_setup.mock'; +import { createCoreStartMock } from './core_start.mock'; + +import { createInternalCorePrebootMock } from './internal_core_preboot.mock'; +import { createInternalCoreSetupMock } from './internal_core_setup.mock'; +import { createInternalCoreStartMock } from './internal_core_start.mock'; + +export const coreLifecycleMock = { + createPreboot: createCorePrebootMock, + createCoreSetup: createCoreSetupMock, + createCoreStart: createCoreStartMock, +}; + +export const coreInternalLifecycleMock = { + createInternalPreboot: createInternalCorePrebootMock, + createInternalSetup: createInternalCoreSetupMock, + createInternalStart: createInternalCoreStartMock, +}; diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/src/internal_core_preboot.mock.ts b/packages/core/lifecycle/core-lifecycle-server-mocks/src/internal_core_preboot.mock.ts new file mode 100644 index 0000000000000..08c6d269f1e38 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/src/internal_core_preboot.mock.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 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 or the Server + * Side Public License, v 1. + */ + +import { analyticsServiceMock } from '@kbn/core-analytics-server-mocks'; +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { contextServiceMock } from '@kbn/core-http-context-server-mocks'; +import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; +import { httpServiceMock } from '@kbn/core-http-server-mocks'; +import { loggingServiceMock } from '@kbn/core-logging-server-mocks'; +import { prebootServiceMock } from '@kbn/core-preboot-server-mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; + +export function createInternalCorePrebootMock() { + const prebootDeps = { + analytics: analyticsServiceMock.createAnalyticsServicePreboot(), + context: contextServiceMock.createPrebootContract(), + elasticsearch: elasticsearchServiceMock.createInternalPreboot(), + http: httpServiceMock.createInternalPrebootContract(), + httpResources: httpResourcesMock.createPrebootContract(), + uiSettings: uiSettingsServiceMock.createPrebootContract(), + logging: loggingServiceMock.createInternalPrebootContract(), + preboot: prebootServiceMock.createInternalPrebootContract(), + }; + return prebootDeps; +} diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/src/internal_core_setup.mock.ts b/packages/core/lifecycle/core-lifecycle-server-mocks/src/internal_core_setup.mock.ts new file mode 100644 index 0000000000000..cddb9a49dab3c --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/src/internal_core_setup.mock.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 or the Server + * Side Public License, v 1. + */ + +import { analyticsServiceMock } from '@kbn/core-analytics-server-mocks'; +import { capabilitiesServiceMock } from '@kbn/core-capabilities-server-mocks'; +import { deprecationsServiceMock } from '@kbn/core-deprecations-server-mocks'; +import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { environmentServiceMock } from '@kbn/core-environment-server-mocks'; +import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks'; +import { contextServiceMock } from '@kbn/core-http-context-server-mocks'; +import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; +import { httpServiceMock } from '@kbn/core-http-server-mocks'; +import { i18nServiceMock } from '@kbn/core-i18n-server-mocks'; +import { loggingServiceMock } from '@kbn/core-logging-server-mocks'; +import { metricsServiceMock } from '@kbn/core-metrics-server-mocks'; +import { renderingServiceMock } from '@kbn/core-rendering-server-mocks'; +import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; +import { statusServiceMock } from '@kbn/core-status-server-mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; +import { coreUsageDataServiceMock } from '@kbn/core-usage-data-server-mocks'; + +export function createInternalCoreSetupMock() { + const setupDeps = { + analytics: analyticsServiceMock.createAnalyticsServiceSetup(), + capabilities: capabilitiesServiceMock.createSetupContract(), + context: contextServiceMock.createSetupContract(), + docLinks: docLinksServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createInternalSetup(), + http: httpServiceMock.createInternalSetupContract(), + savedObjects: savedObjectsServiceMock.createInternalSetupContract(), + status: statusServiceMock.createInternalSetupContract(), + environment: environmentServiceMock.createSetupContract(), + i18n: i18nServiceMock.createSetupContract(), + httpResources: httpResourcesMock.createSetupContract(), + rendering: renderingServiceMock.createSetupContract(), + uiSettings: uiSettingsServiceMock.createSetupContract(), + logging: loggingServiceMock.createInternalSetupContract(), + metrics: metricsServiceMock.createInternalSetupContract(), + deprecations: deprecationsServiceMock.createInternalSetupContract(), + executionContext: executionContextServiceMock.createInternalSetupContract(), + coreUsageData: coreUsageDataServiceMock.createSetupContract(), + }; + return setupDeps; +} diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/src/internal_core_start.mock.ts b/packages/core/lifecycle/core-lifecycle-server-mocks/src/internal_core_start.mock.ts new file mode 100644 index 0000000000000..6283fa3ce6a88 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/src/internal_core_start.mock.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 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 or the Server + * Side Public License, v 1. + */ + +import { analyticsServiceMock } from '@kbn/core-analytics-server-mocks'; +import { capabilitiesServiceMock } from '@kbn/core-capabilities-server-mocks'; +import { deprecationsServiceMock } from '@kbn/core-deprecations-server-mocks'; +import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; +import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks'; +import { httpServiceMock } from '@kbn/core-http-server-mocks'; +import { metricsServiceMock } from '@kbn/core-metrics-server-mocks'; +import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; +import { coreUsageDataServiceMock } from '@kbn/core-usage-data-server-mocks'; + +export function createInternalCoreStartMock() { + const startDeps = { + analytics: analyticsServiceMock.createAnalyticsServiceStart(), + capabilities: capabilitiesServiceMock.createStartContract(), + docLinks: docLinksServiceMock.createStartContract(), + elasticsearch: elasticsearchServiceMock.createInternalStart(), + http: httpServiceMock.createInternalStartContract(), + metrics: metricsServiceMock.createInternalStartContract(), + savedObjects: savedObjectsServiceMock.createInternalStartContract(), + uiSettings: uiSettingsServiceMock.createStartContract(), + coreUsageData: coreUsageDataServiceMock.createStartContract(), + executionContext: executionContextServiceMock.createInternalStartContract(), + deprecations: deprecationsServiceMock.createInternalStartContract(), + }; + return startDeps; +} diff --git a/packages/core/lifecycle/core-lifecycle-server-mocks/tsconfig.json b/packages/core/lifecycle/core-lifecycle-server-mocks/tsconfig.json new file mode 100644 index 0000000000000..71bb40fe57f3f --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server-mocks/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/core/lifecycle/core-lifecycle-server/BUILD.bazel b/packages/core/lifecycle/core-lifecycle-server/BUILD.bazel new file mode 100644 index 0000000000000..85b3b6ab1ca97 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server/BUILD.bazel @@ -0,0 +1,122 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-lifecycle-server" +PKG_REQUIRE_NAME = "@kbn/core-lifecycle-server" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/core/analytics/core-analytics-server:npm_module_types", + "//packages/core/capabilities/core-capabilities-server:npm_module_types", + "//packages/core/deprecations/core-deprecations-server:npm_module_types", + "//packages/core/doc-links/core-doc-links-server:npm_module_types", + "//packages/core/elasticsearch/core-elasticsearch-server:npm_module_types", + "//packages/core/execution-context/core-execution-context-server:npm_module_types", + "//packages/core/http/core-http-server:npm_module_types", + "//packages/core/http/core-http-request-handler-context-server:npm_module_types", + "//packages/core/http/core-http-resources-server:npm_module_types", + "//packages/core/i18n/core-i18n-server:npm_module_types", + "//packages/core/logging/core-logging-server:npm_module_types", + "//packages/core/metrics/core-metrics-server:npm_module_types", + "//packages/core/preboot/core-preboot-server:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-server:npm_module_types", + "//packages/core/status/core-status-server:npm_module_types", + "//packages/core/ui-settings/core-ui-settings-server:npm_module_types", + "//packages/core/usage-data/core-usage-data-server:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/lifecycle/core-lifecycle-server/README.md b/packages/core/lifecycle/core-lifecycle-server/README.md new file mode 100644 index 0000000000000..5d06aa257f81f --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server/README.md @@ -0,0 +1,7 @@ +# @kbn/core-lifecycle-server + +This package contains the public types for core's server-side lifecycle contracts and services accessor: +- `CorePreboot` +- `CoreSetup` +- `CoreStart` +- `StartServicesAccessor` diff --git a/packages/core/lifecycle/core-lifecycle-server/index.ts b/packages/core/lifecycle/core-lifecycle-server/index.ts new file mode 100644 index 0000000000000..5e8b379724036 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server/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 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 or the Server + * Side Public License, v 1. + */ + +export type { CorePreboot, CoreSetup, CoreStart, StartServicesAccessor } from './src'; diff --git a/packages/core/lifecycle/core-lifecycle-server/jest.config.js b/packages/core/lifecycle/core-lifecycle-server/jest.config.js new file mode 100644 index 0000000000000..3d48a32bd7610 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server/jest.config.js @@ -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 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 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['/packages/core/lifecycle/core-lifecycle-server'], +}; diff --git a/packages/core/lifecycle/core-lifecycle-server/kibana.jsonc b/packages/core/lifecycle/core-lifecycle-server/kibana.jsonc new file mode 100644 index 0000000000000..867db6cc2dab0 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-lifecycle-server", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/lifecycle/core-lifecycle-server/package.json b/packages/core/lifecycle/core-lifecycle-server/package.json new file mode 100644 index 0000000000000..da5e093f9c250 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-lifecycle-server", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/lifecycle/core-lifecycle-server/src/core_preboot.ts b/packages/core/lifecycle/core-lifecycle-server/src/core_preboot.ts new file mode 100644 index 0000000000000..893854149d7d2 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server/src/core_preboot.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 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 or the Server + * Side Public License, v 1. + */ + +import type { AnalyticsServicePreboot } from '@kbn/core-analytics-server'; +import type { HttpServicePreboot } from '@kbn/core-http-server'; +import type { PrebootServicePreboot } from '@kbn/core-preboot-server'; +import type { ElasticsearchServicePreboot } from '@kbn/core-elasticsearch-server'; +import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; + +/** + * Context passed to the `setup` method of `preboot` plugins. + * @public + */ +export interface CorePreboot { + /** {@link AnalyticsServicePreboot} */ + analytics: AnalyticsServicePreboot; + /** {@link ElasticsearchServicePreboot} */ + elasticsearch: ElasticsearchServicePreboot; + /** {@link HttpServicePreboot} */ + http: HttpServicePreboot; + /** {@link PrebootServicePreboot} */ + preboot: PrebootServicePreboot; +} diff --git a/packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts b/packages/core/lifecycle/core-lifecycle-server/src/core_setup.ts new file mode 100644 index 0000000000000..1565e11b13777 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server/src/core_setup.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 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 or the Server + * Side Public License, v 1. + */ + +import { AnalyticsServiceSetup } from '@kbn/core-analytics-server'; +import { CapabilitiesSetup } from '@kbn/core-capabilities-server'; +import { DeprecationsServiceSetup } from '@kbn/core-deprecations-server'; +import { DocLinksServiceSetup } from '@kbn/core-doc-links-server'; +import { ElasticsearchServiceSetup } from '@kbn/core-elasticsearch-server'; +import { ExecutionContextSetup } from '@kbn/core-execution-context-server'; +import { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; +import { HttpResources } from '@kbn/core-http-resources-server'; +import { HttpServiceSetup } from '@kbn/core-http-server'; +import { I18nServiceSetup } from '@kbn/core-i18n-server'; +import { LoggingServiceSetup } from '@kbn/core-logging-server'; +import { MetricsServiceSetup } from '@kbn/core-metrics-server'; +import { SavedObjectsServiceSetup } from '@kbn/core-saved-objects-server'; +import { StatusServiceSetup } from '@kbn/core-status-server'; +import { UiSettingsServiceSetup } from '@kbn/core-ui-settings-server'; +import { CoreUsageDataSetup } from '@kbn/core-usage-data-server'; +import { CoreStart } from './core_start'; + +/** + * Context passed to the `setup` method of `standard` plugins. + * + * @typeParam TPluginsStart - the type of the consuming plugin's start dependencies. Should be the same + * as the consuming {@link Plugin}'s `TPluginsStart` type. Used by `getStartServices`. + * @typeParam TStart - the type of the consuming plugin's start contract. Should be the same as the + * consuming {@link Plugin}'s `TStart` type. Used by `getStartServices`. + * @public + */ +export interface CoreSetup { + /** {@link AnalyticsServiceSetup} */ + analytics: AnalyticsServiceSetup; + /** {@link CapabilitiesSetup} */ + capabilities: CapabilitiesSetup; + /** {@link DocLinksServiceSetup} */ + docLinks: DocLinksServiceSetup; + /** {@link ElasticsearchServiceSetup} */ + elasticsearch: ElasticsearchServiceSetup; + /** {@link ExecutionContextSetup} */ + executionContext: ExecutionContextSetup; + /** {@link HttpServiceSetup} */ + http: HttpServiceSetup & { + /** {@link HttpResources} */ + resources: HttpResources; + }; + /** {@link I18nServiceSetup} */ + i18n: I18nServiceSetup; + /** {@link LoggingServiceSetup} */ + logging: LoggingServiceSetup; + /** {@link MetricsServiceSetup} */ + metrics: MetricsServiceSetup; + /** {@link SavedObjectsServiceSetup} */ + savedObjects: SavedObjectsServiceSetup; + /** {@link StatusServiceSetup} */ + status: StatusServiceSetup; + /** {@link UiSettingsServiceSetup} */ + uiSettings: UiSettingsServiceSetup; + /** {@link DeprecationsServiceSetup} */ + deprecations: DeprecationsServiceSetup; + /** {@link StartServicesAccessor} */ + getStartServices: StartServicesAccessor; + /** @internal {@link CoreUsageDataSetup} */ + coreUsageData: CoreUsageDataSetup; +} + +/** + * Allows plugins to get access to APIs available in start inside async handlers. + * Promise will not resolve until Core and plugin dependencies have completed `start`. + * This should only be used inside handlers registered during `setup` that will only be executed + * after `start` lifecycle. + * + * @public + */ +export type StartServicesAccessor< + TPluginsStart extends object = object, + TStart = unknown +> = () => Promise<[CoreStart, TPluginsStart, TStart]>; diff --git a/packages/core/lifecycle/core-lifecycle-server/src/core_start.ts b/packages/core/lifecycle/core-lifecycle-server/src/core_start.ts new file mode 100644 index 0000000000000..53c989154af08 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server/src/core_start.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 or the Server + * Side Public License, v 1. + */ + +import { AnalyticsServiceStart } from '@kbn/core-analytics-server'; +import { CapabilitiesStart } from '@kbn/core-capabilities-server'; +import { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import { ElasticsearchServiceStart } from '@kbn/core-elasticsearch-server'; +import { ExecutionContextStart } from '@kbn/core-execution-context-server'; +import { HttpServiceStart } from '@kbn/core-http-server'; +import { MetricsServiceStart } from '@kbn/core-metrics-server'; +import { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; +import { UiSettingsServiceStart } from '@kbn/core-ui-settings-server'; +import { CoreUsageDataStart } from '@kbn/core-usage-data-server'; + +/** + * Context passed to the plugins `start` method. + * + * @public + */ +export interface CoreStart { + /** {@link AnalyticsServiceStart} */ + analytics: AnalyticsServiceStart; + /** {@link CapabilitiesStart} */ + capabilities: CapabilitiesStart; + /** {@link DocLinksServiceStart} */ + docLinks: DocLinksServiceStart; + /** {@link ElasticsearchServiceStart} */ + elasticsearch: ElasticsearchServiceStart; + /** {@link ExecutionContextStart} */ + executionContext: ExecutionContextStart; + /** {@link HttpServiceStart} */ + http: HttpServiceStart; + /** {@link MetricsServiceStart} */ + metrics: MetricsServiceStart; + /** {@link SavedObjectsServiceStart} */ + savedObjects: SavedObjectsServiceStart; + /** {@link UiSettingsServiceStart} */ + uiSettings: UiSettingsServiceStart; + /** @internal {@link CoreUsageDataStart} */ + coreUsageData: CoreUsageDataStart; +} diff --git a/packages/core/lifecycle/core-lifecycle-server/src/index.ts b/packages/core/lifecycle/core-lifecycle-server/src/index.ts new file mode 100644 index 0000000000000..37f4a6c60fd14 --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server/src/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 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 or the Server + * Side Public License, v 1. + */ +export type { CorePreboot } from './core_preboot'; +export type { CoreSetup, StartServicesAccessor } from './core_setup'; +export type { CoreStart } from './core_start'; diff --git a/packages/core/lifecycle/core-lifecycle-server/tsconfig.json b/packages/core/lifecycle/core-lifecycle-server/tsconfig.json new file mode 100644 index 0000000000000..71bb40fe57f3f --- /dev/null +++ b/packages/core/lifecycle/core-lifecycle-server/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/core/logging/core-logging-server-internal/src/appenders/rewrite/policies/meta/meta_policy.test.ts b/packages/core/logging/core-logging-server-internal/src/appenders/rewrite/policies/meta/meta_policy.test.ts index 3c12e479cc834..86d4df2d1d709 100644 --- a/packages/core/logging/core-logging-server-internal/src/appenders/rewrite/policies/meta/meta_policy.test.ts +++ b/packages/core/logging/core-logging-server-internal/src/appenders/rewrite/policies/meta/meta_policy.test.ts @@ -124,7 +124,7 @@ describe('MetaRewritePolicy', () => { "error": Object {}, "tags": Array [ "0", - undefined, + , ], } `); diff --git a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts index 351e2aca43f56..b17cb6c3195ae 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts @@ -30,7 +30,7 @@ describe('MetricsService', () => { let metricsService: MetricsService; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const configService = configServiceMock.create({ atPath: { interval: moment.duration(testInterval) }, diff --git a/packages/core/overlays/core-overlays-browser-internal/src/banners/user_banner_service.test.ts b/packages/core/overlays/core-overlays-browser-internal/src/banners/user_banner_service.test.ts index 378de1611a746..1936211774231 100644 --- a/packages/core/overlays/core-overlays-browser-internal/src/banners/user_banner_service.test.ts +++ b/packages/core/overlays/core-overlays-browser-internal/src/banners/user_banner_service.test.ts @@ -56,7 +56,7 @@ describe('OverlayBannersService', () => { }); it('dismisses banner after timeout', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startService('testing banner!'); expect(banners.remove).not.toHaveBeenCalled(); diff --git a/packages/core/plugins/core-plugins-server-internal/BUILD.bazel b/packages/core/plugins/core-plugins-server-internal/BUILD.bazel new file mode 100644 index 0000000000000..61044f11d5b51 --- /dev/null +++ b/packages/core/plugins/core-plugins-server-internal/BUILD.bazel @@ -0,0 +1,155 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-plugins-server-internal" +PKG_REQUIRE_NAME = "@kbn/core-plugins-server-internal" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "@npm//moment", + "@npm//rxjs", + "@npm//semver", + "@npm//type-detect", + "@npm//lodash", + "//packages/kbn-std", + "//packages/kbn-config", + "//packages/kbn-config-schema", + "//packages/kbn-logging", + "//packages/kbn-utils", + "//packages/core/base/core-base-common", + "//packages/core/base/core-base-server-internal", + "//packages/core/lifecycle/core-lifecycle-server-internal", + "//packages/core/elasticsearch/core-elasticsearch-server-internal", + "//packages/core/node/core-node-server", + "//packages/core/saved-objects/core-saved-objects-base-server-internal", + # test dependencies + "@npm//mock-fs", + "//packages/kbn-config-mocks", + "//packages/core/base/core-base-server-mocks", + "//packages/core/lifecycle/core-lifecycle-server-mocks", + "//packages/core/logging/core-logging-server-mocks", + "//packages/core/node/core-node-server-mocks", + "//packages/core/plugins/core-plugins-server", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "@npm//moment", + "@npm//rxjs", + "@npm//semver", + "@npm//type-detect", + "@npm//lodash", + "//packages/kbn-std:npm_module_types", + "//packages/kbn-config:npm_module_types", + "//packages/kbn-config-schema:npm_module_types", + "//packages/kbn-logging:npm_module_types", + "//packages/kbn-utils:npm_module_types", + "//packages/core/base/core-base-common:npm_module_types", + "//packages/core/base/core-base-server-internal:npm_module_types", + "//packages/core/elasticsearch/core-elasticsearch-server-internal:npm_module_types", + "//packages/core/node/core-node-server:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-base-server-internal:npm_module_types", + "//packages/core/http/core-http-server:npm_module_types", + "//packages/core/http/core-http-request-handler-context-server:npm_module_types", + "//packages/core/lifecycle/core-lifecycle-server:npm_module_types", + "//packages/core/lifecycle/core-lifecycle-server-internal:npm_module_types", + "//packages/core/plugins/core-plugins-server:npm_module_types", + # test dependencies' mocks + "@npm//mock-fs", + "//packages/kbn-config-mocks:npm_module_types", + "//packages/core/base/core-base-server-mocks:npm_module_types", + "//packages/core/lifecycle/core-lifecycle-server-mocks:npm_module_types", + "//packages/core/logging/core-logging-server-mocks:npm_module_types", + "//packages/core/node/core-node-server-mocks:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/plugins/core-plugins-server-internal/README.md b/packages/core/plugins/core-plugins-server-internal/README.md new file mode 100644 index 0000000000000..7ca4a5600877f --- /dev/null +++ b/packages/core/plugins/core-plugins-server-internal/README.md @@ -0,0 +1,3 @@ +# @kbn/core-plugins-server-internal + +This package contains the internal types and implementation for Core's server-side `plugins` service. diff --git a/packages/core/plugins/core-plugins-server-internal/index.ts b/packages/core/plugins/core-plugins-server-internal/index.ts new file mode 100644 index 0000000000000..072ffda4b4421 --- /dev/null +++ b/packages/core/plugins/core-plugins-server-internal/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 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 or the Server + * Side Public License, v 1. + */ + +export { PluginsService, PluginWrapper, config, isNewPlatformPlugin } from './src'; +export type { + PluginsServiceSetup, + PluginsServiceStart, + DiscoveredPlugins, + PluginDependencies, +} from './src'; diff --git a/packages/core/plugins/core-plugins-server-internal/jest.config.js b/packages/core/plugins/core-plugins-server-internal/jest.config.js new file mode 100644 index 0000000000000..08315583a6f6d --- /dev/null +++ b/packages/core/plugins/core-plugins-server-internal/jest.config.js @@ -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 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 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['/packages/core/plugins/core-plugins-server-internal'], +}; diff --git a/packages/core/plugins/core-plugins-server-internal/kibana.jsonc b/packages/core/plugins/core-plugins-server-internal/kibana.jsonc new file mode 100644 index 0000000000000..2354b5ea2054e --- /dev/null +++ b/packages/core/plugins/core-plugins-server-internal/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-plugins-server-internal", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/plugins/core-plugins-server-internal/package.json b/packages/core/plugins/core-plugins-server-internal/package.json new file mode 100644 index 0000000000000..68adae5d08fed --- /dev/null +++ b/packages/core/plugins/core-plugins-server-internal/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-plugins-server-internal", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/server/plugins/create_browser_config.test.ts b/packages/core/plugins/core-plugins-server-internal/src/create_browser_config.test.ts similarity index 98% rename from src/core/server/plugins/create_browser_config.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/create_browser_config.test.ts index ca0366b7477fa..11f55d1bc1edb 100644 --- a/src/core/server/plugins/create_browser_config.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/create_browser_config.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { PluginConfigDescriptor } from './types'; +import type { PluginConfigDescriptor } from '@kbn/core-plugins-server'; import { createBrowserConfig } from './create_browser_config'; import { schema, TypeOf } from '@kbn/config-schema'; diff --git a/src/core/server/plugins/create_browser_config.ts b/packages/core/plugins/core-plugins-server-internal/src/create_browser_config.ts similarity index 98% rename from src/core/server/plugins/create_browser_config.ts rename to packages/core/plugins/core-plugins-server-internal/src/create_browser_config.ts index 0bf812d2e5cce..05844839934b2 100644 --- a/src/core/server/plugins/create_browser_config.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/create_browser_config.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ExposedToBrowserDescriptor, PluginConfigDescriptor } from './types'; +import { ExposedToBrowserDescriptor, PluginConfigDescriptor } from '@kbn/core-plugins-server'; export const createBrowserConfig = ( config: T, diff --git a/src/core/server/plugins/discovery/index.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/index.ts similarity index 100% rename from src/core/server/plugins/discovery/index.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/index.ts diff --git a/src/core/server/plugins/discovery/is_camel_case.test.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/is_camel_case.test.ts similarity index 100% rename from src/core/server/plugins/discovery/is_camel_case.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/is_camel_case.test.ts diff --git a/src/core/server/plugins/discovery/is_camel_case.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/is_camel_case.ts similarity index 100% rename from src/core/server/plugins/discovery/is_camel_case.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/is_camel_case.ts diff --git a/src/core/server/plugins/discovery/plugin_discovery_error.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_discovery_error.ts similarity index 100% rename from src/core/server/plugins/discovery/plugin_discovery_error.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_discovery_error.ts diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.test.mocks.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.test.mocks.ts similarity index 100% rename from src/core/server/plugins/discovery/plugin_manifest_parser.test.mocks.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.test.mocks.ts diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.test.ts similarity index 100% rename from src/core/server/plugins/discovery/plugin_manifest_parser.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.test.ts diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts similarity index 99% rename from src/core/server/plugins/discovery/plugin_manifest_parser.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts index 0a54899856ac1..5402b9218620d 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugin_manifest_parser.ts @@ -13,7 +13,7 @@ import { promisify } from 'util'; import { snakeCase } from 'lodash'; import { isConfigPath, PackageInfo } from '@kbn/config'; import { PluginType } from '@kbn/core-base-common'; -import { PluginManifest } from '../types'; +import { PluginManifest } from '@kbn/core-plugins-server'; import { PluginDiscoveryError } from './plugin_discovery_error'; import { isCamelCase } from './is_camel_case'; diff --git a/src/core/server/plugins/discovery/plugins_discovery.test.mocks.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugins_discovery.test.mocks.ts similarity index 100% rename from src/core/server/plugins/discovery/plugins_discovery.test.mocks.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/plugins_discovery.test.mocks.ts diff --git a/src/core/server/plugins/discovery/plugins_discovery.test.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugins_discovery.test.ts similarity index 99% rename from src/core/server/plugins/discovery/plugins_discovery.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/plugins_discovery.test.ts index 03c86fb46b5eb..8c1c50e8a612c 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugins_discovery.test.ts @@ -22,7 +22,7 @@ import type { NodeInfo } from '@kbn/core-node-server'; import { PluginsConfig, PluginsConfigType, config } from '../plugins_config'; import type { InstanceInfo } from '../plugin_context'; import { discover } from './plugins_discovery'; -import { PluginType } from '../types'; +import { PluginType } from '@kbn/core-base-common'; const KIBANA_ROOT = process.cwd(); diff --git a/src/core/server/plugins/discovery/plugins_discovery.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugins_discovery.ts similarity index 100% rename from src/core/server/plugins/discovery/plugins_discovery.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/plugins_discovery.ts diff --git a/src/core/server/plugins/discovery/scan_plugin_search_paths.test.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/scan_plugin_search_paths.test.ts similarity index 100% rename from src/core/server/plugins/discovery/scan_plugin_search_paths.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/scan_plugin_search_paths.test.ts diff --git a/src/core/server/plugins/discovery/scan_plugin_search_paths.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/scan_plugin_search_paths.ts similarity index 100% rename from src/core/server/plugins/discovery/scan_plugin_search_paths.ts rename to packages/core/plugins/core-plugins-server-internal/src/discovery/scan_plugin_search_paths.ts diff --git a/src/core/server/plugins/index.ts b/packages/core/plugins/core-plugins-server-internal/src/index.ts similarity index 87% rename from src/core/server/plugins/index.ts rename to packages/core/plugins/core-plugins-server-internal/src/index.ts index 2111d467ef3c2..3eb852b641fcd 100644 --- a/src/core/server/plugins/index.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/index.ts @@ -15,4 +15,5 @@ export type { export { config } from './plugins_config'; /** @internal */ export { isNewPlatformPlugin } from './discovery'; -export * from './types'; +export type { PluginDependencies } from './types'; +export { PluginWrapper } from './plugin'; diff --git a/src/core/server/plugins/legacy_config.test.ts b/packages/core/plugins/core-plugins-server-internal/src/legacy_config.test.ts similarity index 66% rename from src/core/server/plugins/legacy_config.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/legacy_config.test.ts index ca7a2d8a5454e..2bd50db020d0e 100644 --- a/src/core/server/plugins/legacy_config.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/legacy_config.test.ts @@ -5,37 +5,17 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - import { take } from 'rxjs/operators'; -import { ConfigService, Env } from '@kbn/config'; -import { getEnvOptions, rawConfigServiceMock } from '@kbn/config-mocks'; import { getGlobalConfig, getGlobalConfig$ } from './legacy_config'; -import { REPO_ROOT } from '@kbn/utils'; -import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { duration } from 'moment'; import { fromRoot } from '@kbn/utils'; import { ByteSizeValue } from '@kbn/config-schema'; -import { Server } from '../server'; +import { createCoreContextConfigServiceMock } from './test_helpers'; describe('Legacy config', () => { - let env: Env; - let logger: ReturnType; - - beforeEach(() => { - env = Env.createDefault(REPO_ROOT, getEnvOptions()); - logger = loggingSystemMock.create(); - }); - - const createConfigService = (rawConfig: Record = {}): ConfigService => { - const rawConfigService = rawConfigServiceMock.create({ rawConfig }); - const server = new Server(rawConfigService, env, logger); - server.setupCoreConfig(); - return server.configService; - }; - describe('getGlobalConfig', () => { it('should return the global config', async () => { - const configService = createConfigService(); + const configService = createCoreContextConfigServiceMock(); await configService.validate(); const legacyConfig = getGlobalConfig(configService); @@ -54,7 +34,7 @@ describe('Legacy config', () => { describe('getGlobalConfig$', () => { it('should return an observable for the global config', async () => { - const configService = createConfigService(); + const configService = createCoreContextConfigServiceMock(); const legacyConfig = await getGlobalConfig$(configService).pipe(take(1)).toPromise(); diff --git a/src/core/server/plugins/legacy_config.ts b/packages/core/plugins/core-plugins-server-internal/src/legacy_config.ts similarity index 96% rename from src/core/server/plugins/legacy_config.ts rename to packages/core/plugins/core-plugins-server-internal/src/legacy_config.ts index de86345c5b7ba..46e792d6b226d 100644 --- a/src/core/server/plugins/legacy_config.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/legacy_config.ts @@ -20,7 +20,7 @@ import { type SavedObjectsConfigType, savedObjectsConfig, } from '@kbn/core-saved-objects-base-server-internal'; -import { SharedGlobalConfig, SharedGlobalConfigKeys } from './types'; +import { SharedGlobalConfig, SharedGlobalConfigKeys } from '@kbn/core-plugins-server'; const createGlobalConfig = ({ elasticsearch, diff --git a/src/core/server/plugins/plugin.test.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin.test.ts similarity index 98% rename from src/core/server/plugins/plugin.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugin.test.ts index 538c9539b2a68..008b8eaf6665a 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin.test.ts @@ -17,10 +17,11 @@ import type { CoreContext } from '@kbn/core-base-server-internal'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import type { NodeInfo } from '@kbn/core-node-server'; import { nodeServiceMock } from '@kbn/core-node-server-mocks'; -import { coreMock } from '../mocks'; - +import type { PluginManifest } from '@kbn/core-plugins-server'; import { PluginWrapper } from './plugin'; -import { PluginManifest, PluginType } from './types'; +import { PluginType } from '@kbn/core-base-common'; +import { coreInternalLifecycleMock } from '@kbn/core-lifecycle-server-mocks'; + import { createPluginInitializerContext, createPluginSetupContext, @@ -72,7 +73,7 @@ let coreContext: CoreContext; let instanceInfo: InstanceInfo; let nodeInfo: NodeInfo; -const setupDeps = coreMock.createInternalSetup(); +const setupDeps = coreInternalLifecycleMock.createInternalSetup(); beforeEach(() => { coreId = Symbol('core'); diff --git a/src/core/server/plugins/plugin.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin.ts similarity index 97% rename from src/core/server/plugins/plugin.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugin.ts index 9ddab175d313a..5446e983676c0 100644 --- a/src/core/server/plugins/plugin.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin.ts @@ -12,18 +12,17 @@ import { firstValueFrom, Subject } from 'rxjs'; import { isPromise } from '@kbn/std'; import { isConfigSchema } from '@kbn/config-schema'; import type { Logger } from '@kbn/logging'; -import { PluginType } from '@kbn/core-base-common'; -import { +import { type PluginOpaqueId, PluginType } from '@kbn/core-base-common'; +import type { AsyncPlugin, Plugin, PluginConfigDescriptor, PluginInitializer, PluginInitializerContext, PluginManifest, - PluginOpaqueId, PrebootPlugin, -} from './types'; -import { CorePreboot, CoreSetup, CoreStart } from '..'; +} from '@kbn/core-plugins-server'; +import type { CorePreboot, CoreSetup, CoreStart } from '@kbn/core-lifecycle-server'; const OSS_PATH_REGEX = /[\/|\\]src[\/|\\]plugins[\/|\\]/; // Matches src/plugins directory on POSIX and Windows const XPACK_PATH_REGEX = /[\/|\\]x-pack[\/|\\]plugins[\/|\\]/; // Matches x-pack/plugins directory on POSIX and Windows diff --git a/src/core/server/plugins/plugin_context.test.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.test.ts similarity index 90% rename from src/core/server/plugins/plugin_context.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugin_context.test.ts index 803c5ded6a545..978bf62222f07 100644 --- a/src/core/server/plugins/plugin_context.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.test.ts @@ -8,8 +8,7 @@ import { duration } from 'moment'; import { first } from 'rxjs/operators'; -import { REPO_ROOT } from '@kbn/utils'; -import { fromRoot } from '@kbn/utils'; +import { REPO_ROOT, fromRoot } from '@kbn/utils'; import { rawConfigServiceMock, getEnvOptions, configServiceMock } from '@kbn/config-mocks'; import type { CoreContext } from '@kbn/core-base-server-internal'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; @@ -21,12 +20,15 @@ import { InstanceInfo, } from './plugin_context'; -import { PluginManifest, PluginType } from './types'; -import { Server } from '../server'; +import { PluginType } from '@kbn/core-base-common'; +import { PluginManifest } from '@kbn/core-plugins-server'; import { schema, ByteSizeValue } from '@kbn/config-schema'; import { ConfigService, Env } from '@kbn/config'; import { PluginWrapper } from './plugin'; -import { coreMock } from '../mocks'; + +import { coreInternalLifecycleMock } from '@kbn/core-lifecycle-server-mocks'; +import { mockCoreContext } from '@kbn/core-base-server-mocks'; +import { createCoreContextConfigServiceMock } from './test_helpers'; function createPluginManifest(manifestProps: Partial = {}): PluginManifest { return { @@ -54,7 +56,6 @@ describe('createPluginInitializerContext', () => { let opaqueId: symbol; let env: Env; let coreContext: CoreContext; - let server: Server; let instanceInfo: InstanceInfo; let nodeInfo: NodeInfo; @@ -67,10 +68,11 @@ describe('createPluginInitializerContext', () => { }; nodeInfo = nodeServiceMock.createInternalPrebootContract(); env = Env.createDefault(REPO_ROOT, getEnvOptions()); - const config$ = rawConfigServiceMock.create({ rawConfig: {} }); - server = new Server(config$, env, logger); - server.setupCoreConfig(); - coreContext = { coreId, env, logger, configService: server.configService }; + coreContext = mockCoreContext.create({ + env, + logger, + configService: configServiceMock.create(), + }); }); describe('context.config', () => { @@ -115,7 +117,12 @@ describe('createPluginInitializerContext', () => { }); it('config.globalConfig$ should be an observable for the global config', async () => { + const configService = createCoreContextConfigServiceMock(); + + coreContext = { coreId, env, logger, configService }; + const manifest = createPluginManifest(); + const pluginInitializerContext = createPluginInitializerContext({ coreContext, opaqueId, @@ -229,7 +236,7 @@ describe('createPluginPrebootSetupContext', () => { }), }); - const corePreboot = coreMock.createInternalPreboot(); + const corePreboot = coreInternalLifecycleMock.createInternalPreboot(); const prebootSetupContext = createPluginPrebootSetupContext(coreContext, corePreboot, plugin); const holdSetupPromise = Promise.resolve(undefined); diff --git a/src/core/server/plugins/plugin_context.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts similarity index 97% rename from src/core/server/plugins/plugin_context.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts index 97b165617b68e..d1bec41373c9d 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts @@ -11,16 +11,16 @@ import type { CoreContext } from '@kbn/core-base-server-internal'; import type { PluginOpaqueId } from '@kbn/core-base-common'; import type { NodeInfo } from '@kbn/core-node-server'; import type { IRouter, IContextProvider } from '@kbn/core-http-server'; -import type { RequestHandlerContext } from '..'; +import { PluginInitializerContext, PluginManifest } from '@kbn/core-plugins-server'; +import { CorePreboot, CoreSetup, CoreStart } from '@kbn/core-lifecycle-server'; +import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; import { PluginWrapper } from './plugin'; import { PluginsServicePrebootSetupDeps, PluginsServiceSetupDeps, PluginsServiceStartDeps, } from './plugins_service'; -import { PluginInitializerContext, PluginManifest } from './types'; import { getGlobalConfig, getGlobalConfig$ } from './legacy_config'; -import { CorePreboot, CoreSetup, CoreStart } from '..'; /** @internal */ export interface InstanceInfo { diff --git a/src/core/server/plugins/plugins_config.test.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_config.test.ts similarity index 100% rename from src/core/server/plugins/plugins_config.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugins_config.test.ts diff --git a/src/core/server/plugins/plugins_config.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_config.ts similarity index 100% rename from src/core/server/plugins/plugins_config.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugins_config.ts diff --git a/src/core/server/plugins/plugins_service.test.mocks.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.mocks.ts similarity index 100% rename from src/core/server/plugins/plugins_service.test.mocks.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.mocks.ts diff --git a/src/core/server/plugins/plugins_service.test.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts similarity index 99% rename from src/core/server/plugins/plugins_service.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts index 9e234b871d647..4664db6e710c7 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts @@ -19,14 +19,15 @@ import { rawConfigServiceMock, getEnvOptions } from '@kbn/config-mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { environmentServiceMock } from '@kbn/core-environment-server-mocks'; import { nodeServiceMock } from '@kbn/core-node-server-mocks'; -import { coreMock } from '../mocks'; +import { coreInternalLifecycleMock } from '@kbn/core-lifecycle-server-mocks'; import { PluginDiscoveryError } from './discovery'; import { PluginWrapper } from './plugin'; import { PluginsService } from './plugins_service'; import { PluginsSystem } from './plugins_system'; import { config } from './plugins_config'; import { take } from 'rxjs/operators'; -import { DiscoveredPlugin, PluginConfigDescriptor, PluginType } from './types'; +import type { PluginConfigDescriptor } from '@kbn/core-plugins-server'; +import { DiscoveredPlugin, PluginType } from '@kbn/core-base-common'; const MockPluginsSystem: jest.Mock> = PluginsSystem as any; @@ -40,9 +41,9 @@ let standardMockPluginSystem: jest.Mocked>; let environmentPreboot: ReturnType; let nodePreboot: ReturnType; -const prebootDeps = coreMock.createInternalPreboot(); -const setupDeps = coreMock.createInternalSetup(); -const startDeps = coreMock.createInternalStart(); +const prebootDeps = coreInternalLifecycleMock.createInternalPreboot(); +const setupDeps = coreInternalLifecycleMock.createInternalSetup(); +const startDeps = coreInternalLifecycleMock.createInternalStart(); const logger = loggingSystemMock.create(); expect.addSnapshotSerializer(createAbsolutePathSerializer()); diff --git a/src/core/server/plugins/plugins_service.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.ts similarity index 97% rename from src/core/server/plugins/plugins_service.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugins_service.ts index 3305ff0a06b43..556cd8331b454 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.ts @@ -14,17 +14,24 @@ import { getFlattenedObject } from '@kbn/std'; import { Logger } from '@kbn/logging'; import type { IConfigService } from '@kbn/config'; import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; -import type { PluginName } from '@kbn/core-base-common'; +import { type PluginName, PluginType } from '@kbn/core-base-common'; import type { InternalEnvironmentServicePreboot } from '@kbn/core-environment-server-internal'; import type { InternalNodeServicePreboot } from '@kbn/core-node-server-internal'; import type { InternalPluginInfo, UiPlugins } from '@kbn/core-plugins-base-server-internal'; +import { + InternalCorePreboot, + InternalCoreSetup, + InternalCoreStart, +} from '@kbn/core-lifecycle-server-internal'; +import { PluginConfigDescriptor } from '@kbn/core-plugins-server'; +import type { DiscoveredPlugin } from '@kbn/core-base-common'; import { discover, PluginDiscoveryError, PluginDiscoveryErrorType } from './discovery'; import { PluginWrapper } from './plugin'; -import { DiscoveredPlugin, PluginConfigDescriptor, PluginDependencies, PluginType } from './types'; + +import type { PluginDependencies } from './types'; import { PluginsConfig, PluginsConfigType } from './plugins_config'; import { PluginsSystem } from './plugins_system'; import { createBrowserConfig } from './create_browser_config'; -import { InternalCorePreboot, InternalCoreSetup, InternalCoreStart } from '../internal_types'; /** @internal */ export type DiscoveredPlugins = { diff --git a/src/core/server/plugins/plugins_system.test.mocks.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_system.test.mocks.ts similarity index 100% rename from src/core/server/plugins/plugins_system.test.mocks.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugins_system.test.mocks.ts diff --git a/src/core/server/plugins/plugins_system.test.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_system.test.ts similarity index 98% rename from src/core/server/plugins/plugins_system.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugins_system.test.ts index 0d55db06c091e..4010d548a6219 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugins_system.test.ts @@ -15,7 +15,7 @@ import { import { BehaviorSubject } from 'rxjs'; import { REPO_ROOT } from '@kbn/utils'; -import type { PluginName } from '@kbn/core-base-common'; +import { type PluginName, PluginType } from '@kbn/core-base-common'; import type { CoreContext } from '@kbn/core-base-server-internal'; import { Logger } from '@kbn/logging'; import { Env } from '@kbn/config'; @@ -23,9 +23,8 @@ import { configServiceMock, getEnvOptions } from '@kbn/config-mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { PluginWrapper } from './plugin'; -import { PluginType } from './types'; import { PluginsSystem } from './plugins_system'; -import { coreMock } from '../mocks'; +import { coreInternalLifecycleMock } from '@kbn/core-lifecycle-server-mocks'; function createPlugin( id: string, @@ -63,9 +62,9 @@ function createPlugin( }); } -const prebootDeps = coreMock.createInternalPreboot(); -const setupDeps = coreMock.createInternalSetup(); -const startDeps = coreMock.createInternalStart(); +const prebootDeps = coreInternalLifecycleMock.createInternalPreboot(); +const setupDeps = coreInternalLifecycleMock.createInternalSetup(); +const startDeps = coreInternalLifecycleMock.createInternalStart(); let pluginsSystem: PluginsSystem; let configService: ReturnType; @@ -552,7 +551,7 @@ test('`startPlugins` only starts plugins that were setup', async () => { describe('setup', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { jest.useRealTimers(); @@ -589,7 +588,7 @@ describe('setup', () => { describe('start', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { jest.useRealTimers(); @@ -748,7 +747,7 @@ describe('asynchronous plugins', () => { describe('stop', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/core/server/plugins/plugins_system.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_system.ts similarity index 97% rename from src/core/server/plugins/plugins_system.ts rename to packages/core/plugins/core-plugins-server-internal/src/plugins_system.ts index 57db8e7c70f49..d7c4df71dd4fc 100644 --- a/src/core/server/plugins/plugins_system.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugins_system.ts @@ -7,17 +7,18 @@ */ import { withTimeout, isPromise } from '@kbn/std'; -import type { PluginName } from '@kbn/core-base-common'; +import type { DiscoveredPlugin, PluginName } from '@kbn/core-base-common'; import type { CoreContext } from '@kbn/core-base-server-internal'; -import { Logger } from '@kbn/logging'; -import { PluginWrapper } from './plugin'; -import { DiscoveredPlugin, PluginDependencies, PluginType } from './types'; +import type { Logger } from '@kbn/logging'; +import { PluginType } from '@kbn/core-base-common'; +import type { PluginWrapper } from './plugin'; +import { type PluginDependencies } from './types'; import { createPluginPrebootSetupContext, createPluginSetupContext, createPluginStartContext, } from './plugin_context'; -import { +import type { PluginsServicePrebootSetupDeps, PluginsServiceSetupDeps, PluginsServiceStartDeps, diff --git a/packages/core/plugins/core-plugins-server-internal/src/test_helpers/create_core_context_config_service.mock.ts b/packages/core/plugins/core-plugins-server-internal/src/test_helpers/create_core_context_config_service.mock.ts new file mode 100644 index 0000000000000..399d45398eefb --- /dev/null +++ b/packages/core/plugins/core-plugins-server-internal/src/test_helpers/create_core_context_config_service.mock.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 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 or the Server + * Side Public License, v 1. + */ + +import { IConfigService } from '@kbn/config'; +import { configServiceMock } from '@kbn/config-mocks'; +import { ByteSizeValue } from '@kbn/config-schema'; +import { fromRoot } from '@kbn/utils'; +import { duration } from 'moment'; +import { from } from 'rxjs'; + +export const createCoreContextConfigServiceMock = (): IConfigService => { + const configService = configServiceMock.create(); + const getPathConfig = (path: string | string[]) => { + switch (path) { + case 'elasticsearch': + return { + shardTimeout: duration(30, 's'), + requestTimeout: duration(30, 's'), + pingTimeout: duration(30, 's'), + someOtherProps: 'unused', + }; + case 'path': + return { data: fromRoot('data'), someOtherProps: 'unused' }; + case 'savedObjects': + return { maxImportPayloadBytes: new ByteSizeValue(26214400), someOtherProps: 'unused' }; + default: + return {}; + } + }; + configService.atPath.mockImplementation((path) => { + return from([getPathConfig(path)]); + }); + configService.atPathSync.mockImplementation((path) => { + return getPathConfig(path); + }); + + return configService; +}; diff --git a/packages/core/plugins/core-plugins-server-internal/src/test_helpers/index.ts b/packages/core/plugins/core-plugins-server-internal/src/test_helpers/index.ts new file mode 100644 index 0000000000000..86ffb0ec8f407 --- /dev/null +++ b/packages/core/plugins/core-plugins-server-internal/src/test_helpers/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 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 or the Server + * Side Public License, v 1. + */ + +export { createCoreContextConfigServiceMock } from './create_core_context_config_service.mock'; diff --git a/src/core/server/plugins/types.test.ts b/packages/core/plugins/core-plugins-server-internal/src/types.test.ts similarity index 96% rename from src/core/server/plugins/types.test.ts rename to packages/core/plugins/core-plugins-server-internal/src/types.test.ts index 4a0e6052a9901..ea1537bf0649e 100644 --- a/src/core/server/plugins/types.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/types.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ExposedToBrowserDescriptor } from './types'; +import type { ExposedToBrowserDescriptor } from '@kbn/core-plugins-server'; describe('ExposedToBrowserDescriptor', () => { interface ConfigType { diff --git a/packages/core/plugins/core-plugins-server-internal/src/types.ts b/packages/core/plugins/core-plugins-server-internal/src/types.ts new file mode 100644 index 0000000000000..def1a27a4c26f --- /dev/null +++ b/packages/core/plugins/core-plugins-server-internal/src/types.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 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 or the Server + * Side Public License, v 1. + */ + +import type { PluginName, PluginOpaqueId } from '@kbn/core-base-common'; + +/** @internal */ +export interface PluginDependencies { + asNames: ReadonlyMap; + asOpaqueIds: ReadonlyMap; +} diff --git a/packages/core/plugins/core-plugins-server-internal/tsconfig.json b/packages/core/plugins/core-plugins-server-internal/tsconfig.json new file mode 100644 index 0000000000000..71bb40fe57f3f --- /dev/null +++ b/packages/core/plugins/core-plugins-server-internal/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/core/plugins/core-plugins-server-mocks/BUILD.bazel b/packages/core/plugins/core-plugins-server-mocks/BUILD.bazel new file mode 100644 index 0000000000000..39ca50e2b847c --- /dev/null +++ b/packages/core/plugins/core-plugins-server-mocks/BUILD.bazel @@ -0,0 +1,106 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-plugins-server-mocks" +PKG_REQUIRE_NAME = "@kbn/core-plugins-server-mocks" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "//packages/core/plugins/core-plugins-server-internal", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/kbn-utility-types:npm_module_types", + "//packages/core/plugins/core-plugins-server-internal:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/plugins/core-plugins-server-mocks/README.md b/packages/core/plugins/core-plugins-server-mocks/README.md new file mode 100644 index 0000000000000..2ec4d6313919f --- /dev/null +++ b/packages/core/plugins/core-plugins-server-mocks/README.md @@ -0,0 +1,4 @@ +# @kbn/core-plugins-server-mocks + +This package contains mocks for Core's server-side `plugins` service. +- `pluginsServiceMock` diff --git a/packages/kbn-adhoc-profiler/index.ts b/packages/core/plugins/core-plugins-server-mocks/index.ts similarity index 85% rename from packages/kbn-adhoc-profiler/index.ts rename to packages/core/plugins/core-plugins-server-mocks/index.ts index 5aa7c8e9526e4..ebf3e8864ef33 100644 --- a/packages/kbn-adhoc-profiler/index.ts +++ b/packages/core/plugins/core-plugins-server-mocks/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { inspectCpuProfile } from './inspect_cpu_profile'; +export { pluginServiceMock } from './src'; diff --git a/packages/core/plugins/core-plugins-server-mocks/jest.config.js b/packages/core/plugins/core-plugins-server-mocks/jest.config.js new file mode 100644 index 0000000000000..f7924be975ac9 --- /dev/null +++ b/packages/core/plugins/core-plugins-server-mocks/jest.config.js @@ -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 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 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['/packages/core/plugins/core-plugins-server-mocks'], +}; diff --git a/packages/core/plugins/core-plugins-server-mocks/kibana.jsonc b/packages/core/plugins/core-plugins-server-mocks/kibana.jsonc new file mode 100644 index 0000000000000..4a1b2c0bd2258 --- /dev/null +++ b/packages/core/plugins/core-plugins-server-mocks/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-plugins-server-mocks", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/plugins/core-plugins-server-mocks/package.json b/packages/core/plugins/core-plugins-server-mocks/package.json new file mode 100644 index 0000000000000..0af107840be65 --- /dev/null +++ b/packages/core/plugins/core-plugins-server-mocks/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-plugins-server-mocks", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/packages/core/plugins/core-plugins-server-mocks/src/index.ts b/packages/core/plugins/core-plugins-server-mocks/src/index.ts new file mode 100644 index 0000000000000..30b3d50c22b74 --- /dev/null +++ b/packages/core/plugins/core-plugins-server-mocks/src/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 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 or the Server + * Side Public License, v 1. + */ + +export { pluginServiceMock } from './plugins_service.mock'; diff --git a/src/core/server/plugins/plugins_service.mock.ts b/packages/core/plugins/core-plugins-server-mocks/src/plugins_service.mock.ts similarity index 93% rename from src/core/server/plugins/plugins_service.mock.ts rename to packages/core/plugins/core-plugins-server-mocks/src/plugins_service.mock.ts index ee7b35a412e80..58c43a4c30eda 100644 --- a/src/core/server/plugins/plugins_service.mock.ts +++ b/packages/core/plugins/core-plugins-server-mocks/src/plugins_service.mock.ts @@ -7,7 +7,7 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import type { PluginsService, PluginsServiceSetup } from './plugins_service'; +import { PluginsService, type PluginsServiceSetup } from '@kbn/core-plugins-server-internal'; type PluginsServiceMock = jest.Mocked>; diff --git a/packages/core/plugins/core-plugins-server-mocks/tsconfig.json b/packages/core/plugins/core-plugins-server-mocks/tsconfig.json new file mode 100644 index 0000000000000..71bb40fe57f3f --- /dev/null +++ b/packages/core/plugins/core-plugins-server-mocks/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/kbn-adhoc-profiler/BUILD.bazel b/packages/core/plugins/core-plugins-server/BUILD.bazel similarity index 67% rename from packages/kbn-adhoc-profiler/BUILD.bazel rename to packages/core/plugins/core-plugins-server/BUILD.bazel index d3ecbb56e657f..ec304498d123c 100644 --- a/packages/kbn-adhoc-profiler/BUILD.bazel +++ b/packages/core/plugins/core-plugins-server/BUILD.bazel @@ -2,8 +2,8 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_DIRNAME = "kbn-adhoc-profiler" -PKG_REQUIRE_NAME = "@kbn/adhoc-profiler" +PKG_DIRNAME = "core-plugins-server" +PKG_REQUIRE_NAME = "@kbn/core-plugins-server" SOURCE_FILES = glob( [ @@ -35,35 +35,26 @@ NPM_MODULE_EXTRA_FILES = [ "package.json", ] -# In this array place runtime dependencies, including other packages and NPM packages -# which must be available for this code to run. -# -# To reference other packages use: -# "//repo/relative/path/to/package" -# eg. "//packages/kbn-utils" -# -# To reference a NPM package use: -# "@npm//name-of-package" -# eg. "@npm//lodash" RUNTIME_DEPS = [ - "@npm//pprof", - "@npm//execa" + "@npm//rxjs", + "//packages/kbn-config-schema", ] -# In this array place dependencies necessary to build the types, which will include the -# :npm_module_types target of other packages and packages from NPM, including @types/* -# packages. -# -# To reference the types for another package use: -# "//repo/relative/path/to/package:npm_module_types" -# eg. "//packages/kbn-utils:npm_module_types" -# -# References to NPM packages work the same as RUNTIME_DEPS TYPES_DEPS = [ "@npm//@types/node", "@npm//@types/jest", - "@npm//pprof", - "@npm//execa" + "@npm//rxjs", + "//packages/kbn-config:npm_module_types", + "//packages/kbn-config-schema:npm_module_types", + "//packages/kbn-utility-types:npm_module_types", + "//packages/kbn-utils:npm_module_types", + "//packages/kbn-logging:npm_module_types", + "//packages/core/base/core-base-common:npm_module_types", + "//packages/core/node/core-node-server:npm_module_types", + "//packages/core/elasticsearch/core-elasticsearch-server-internal:npm_module_types", + "//packages/core/saved-objects/core-saved-objects-base-server-internal:npm_module_types", + "//packages/core/lifecycle/core-lifecycle-server:npm_module_types", + ] jsts_transpiler( diff --git a/packages/core/plugins/core-plugins-server/README.md b/packages/core/plugins/core-plugins-server/README.md new file mode 100644 index 0000000000000..1add32cc3e1d4 --- /dev/null +++ b/packages/core/plugins/core-plugins-server/README.md @@ -0,0 +1,3 @@ +# @kbn/core-plugins-server + +This package contains the public types for core's server-side plugins service. diff --git a/src/core/server/integration_tests/plugins/plugins_service.test.mocks.ts b/packages/core/plugins/core-plugins-server/index.ts similarity index 53% rename from src/core/server/integration_tests/plugins/plugins_service.test.mocks.ts rename to packages/core/plugins/core-plugins-server/index.ts index d20d3bc094ac4..47aa0d04ac87c 100644 --- a/src/core/server/integration_tests/plugins/plugins_service.test.mocks.ts +++ b/packages/core/plugins/core-plugins-server/index.ts @@ -6,13 +6,18 @@ * Side Public License, v 1. */ -export const mockPackage = { - raw: { __dirname: '/tmp' } as any, -}; +export type { + PrebootPlugin, + Plugin, + AsyncPlugin, + PluginConfigDescriptor, + PluginConfigSchema, + PluginInitializer, + PluginInitializerContext, + PluginManifest, + SharedGlobalConfig, + MakeUsageFromSchema, + ExposedToBrowserDescriptor, +} from './src'; -jest.doMock('load-json-file', () => ({ - sync: () => mockPackage.raw, -})); - -export const mockDiscover = jest.fn(); -jest.mock('../../plugins/discovery/plugins_discovery', () => ({ discover: mockDiscover })); +export { SharedGlobalConfigKeys } from './src'; diff --git a/packages/kbn-adhoc-profiler/jest.config.js b/packages/core/plugins/core-plugins-server/jest.config.js similarity index 81% rename from packages/kbn-adhoc-profiler/jest.config.js rename to packages/core/plugins/core-plugins-server/jest.config.js index ec035df5193fd..f03056c0495e2 100644 --- a/packages/kbn-adhoc-profiler/jest.config.js +++ b/packages/core/plugins/core-plugins-server/jest.config.js @@ -8,6 +8,6 @@ module.exports = { preset: '@kbn/test/jest_node', - rootDir: '../..', - roots: ['/packages/kbn-adhoc-profiler'], + rootDir: '../../../..', + roots: ['/packages/core/plugins/core-plugins-server'], }; diff --git a/packages/core/plugins/core-plugins-server/kibana.jsonc b/packages/core/plugins/core-plugins-server/kibana.jsonc new file mode 100644 index 0000000000000..708281a40646b --- /dev/null +++ b/packages/core/plugins/core-plugins-server/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-plugins-server", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/kbn-adhoc-profiler/package.json b/packages/core/plugins/core-plugins-server/package.json similarity index 66% rename from packages/kbn-adhoc-profiler/package.json rename to packages/core/plugins/core-plugins-server/package.json index aeee4a9d1d37b..75fda3c2ef661 100644 --- a/packages/kbn-adhoc-profiler/package.json +++ b/packages/core/plugins/core-plugins-server/package.json @@ -1,7 +1,8 @@ { - "name": "@kbn/adhoc-profiler", + "name": "@kbn/core-plugins-server", "private": true, "version": "1.0.0", "main": "./target_node/index.js", + "author": "Kibana Core", "license": "SSPL-1.0 OR Elastic License 2.0" } diff --git a/packages/core/plugins/core-plugins-server/src/index.ts b/packages/core/plugins/core-plugins-server/src/index.ts new file mode 100644 index 0000000000000..94ad27dedbf12 --- /dev/null +++ b/packages/core/plugins/core-plugins-server/src/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 or the Server + * Side Public License, v 1. + */ + +export type { + PrebootPlugin, + Plugin, + AsyncPlugin, + PluginConfigDescriptor, + PluginConfigSchema, + PluginInitializer, + PluginInitializerContext, + PluginManifest, + SharedGlobalConfig, + MakeUsageFromSchema, + ExposedToBrowserDescriptor, +} from './types'; + +export { SharedGlobalConfigKeys } from './shared_global_config'; diff --git a/packages/core/plugins/core-plugins-server/src/shared_global_config.ts b/packages/core/plugins/core-plugins-server/src/shared_global_config.ts new file mode 100644 index 0000000000000..6329b2576ab21 --- /dev/null +++ b/packages/core/plugins/core-plugins-server/src/shared_global_config.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 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 or the Server + * Side Public License, v 1. + */ + +export const SharedGlobalConfigKeys = { + // We can add more if really needed + elasticsearch: ['shardTimeout', 'requestTimeout', 'pingTimeout'] as const, + path: ['data'] as const, + savedObjects: ['maxImportPayloadBytes'] as const, +}; diff --git a/src/core/server/plugins/types.ts b/packages/core/plugins/core-plugins-server/src/types.ts similarity index 94% rename from src/core/server/plugins/types.ts rename to packages/core/plugins/core-plugins-server/src/types.ts index 699631bc4411e..46773971d35ef 100644 --- a/src/core/server/plugins/types.ts +++ b/packages/core/plugins/core-plugins-server/src/types.ts @@ -8,9 +8,9 @@ import { Observable } from 'rxjs'; import { Type } from '@kbn/config-schema'; -import { RecursiveReadonly } from '@kbn/utility-types'; -import { PathConfigType } from '@kbn/utils'; -import { LoggerFactory } from '@kbn/logging'; +import type { RecursiveReadonly } from '@kbn/utility-types'; +import type { PathConfigType } from '@kbn/utils'; +import type { LoggerFactory } from '@kbn/logging'; import type { ConfigPath, EnvironmentMode, @@ -21,14 +21,10 @@ import type { PluginName, PluginOpaqueId, PluginType } from '@kbn/core-base-comm import type { NodeInfo } from '@kbn/core-node-server'; import type { ElasticsearchConfigType } from '@kbn/core-elasticsearch-server-internal'; import type { SavedObjectsConfigType } from '@kbn/core-saved-objects-base-server-internal'; -import { CorePreboot, CoreSetup, CoreStart } from '..'; - +import type { CorePreboot, CoreSetup, CoreStart } from '@kbn/core-lifecycle-server'; +import { SharedGlobalConfigKeys } from './shared_global_config'; type Maybe = T | undefined; -// re-exporting for now to avoid adapting all imports, will be removed later on in the migration process -export type { PluginName, PluginOpaqueId, DiscoveredPlugin } from '@kbn/core-base-common'; -export { PluginType } from '@kbn/core-base-common'; - /** * Dedicated type for plugin configuration schema. * @@ -131,12 +127,6 @@ export type MakeUsageFromSchema = { : boolean; }; -/** @internal */ -export interface PluginDependencies { - asNames: ReadonlyMap; - asOpaqueIds: ReadonlyMap; -} - /** * Describes the set of required and optional properties plugin can define in its * mandatory JSON manifest file. @@ -303,13 +293,6 @@ export interface AsyncPlugin< stop?(): void; } -export const SharedGlobalConfigKeys = { - // We can add more if really needed - elasticsearch: ['shardTimeout', 'requestTimeout', 'pingTimeout'] as const, - path: ['data'] as const, - savedObjects: ['maxImportPayloadBytes'] as const, -}; - /** * @public */ diff --git a/packages/core/plugins/core-plugins-server/tsconfig.json b/packages/core/plugins/core-plugins-server/tsconfig.json new file mode 100644 index 0000000000000..71bb40fe57f3f --- /dev/null +++ b/packages/core/plugins/core-plugins-server/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index e906f119a6575..83854c3aafdab 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ +import { setImmediate } from 'timers/promises'; import { join } from 'path'; import loadJsonFile from 'load-json-file'; -import { setImmediate } from 'timers/promises'; import { clientProviderInstanceMock, diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/cache.test.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/cache.test.ts index f4829d33dd714..06a91ce079e60 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/cache.test.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/cache.test.ts @@ -10,7 +10,7 @@ import { Cache } from './cache'; describe('Cache', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { diff --git a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.ts b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.ts index 0467897d2fde6..87a43026b6149 100644 --- a/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.ts +++ b/packages/core/ui-settings/core-ui-settings-server-internal/src/ui_settings_client.test.ts @@ -707,7 +707,7 @@ describe('ui settings', () => { describe('caching', () => { describe('read operations cache user config', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { diff --git a/packages/kbn-ace/BUILD.bazel b/packages/kbn-ace/BUILD.bazel index 91900928a6bca..7f30af32afa95 100644 --- a/packages/kbn-ace/BUILD.bazel +++ b/packages/kbn-ace/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-ace" +PKG_DIRNAME = "kbn-ace" PKG_REQUIRE_NAME = "@kbn/ace" SOURCE_FILES = glob( @@ -97,7 +97,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -106,9 +106,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-adhoc-profiler/README.md b/packages/kbn-adhoc-profiler/README.md deleted file mode 100644 index 688102937385c..0000000000000 --- a/packages/kbn-adhoc-profiler/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# @kbn/adhoc-profiler - -This package offers tools for ad hoc profiling. Currently it only exports one method: `inspectCpuProfile`, which will start a CPU profile before executing the callback it is given, and opens the collected profile in a web browser. It assumes that you have `go` and [`pprof`](https://github.com/google/pprof) installed. - -Profiles are stored in the user's temporary directory (returned from `os.tmpdir()`). diff --git a/packages/kbn-adhoc-profiler/inspect_cpu_profile.ts b/packages/kbn-adhoc-profiler/inspect_cpu_profile.ts deleted file mode 100644 index e48c6989d0d00..0000000000000 --- a/packages/kbn-adhoc-profiler/inspect_cpu_profile.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 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 or the Server - * Side Public License, v 1. - */ -import Fs from 'fs'; -import Os from 'os'; -import Path from 'path'; - -import execa from 'execa'; - -import { pprof } from './require_pprof'; -import { withCpuProfile } from './with_cpu_profile'; - -export function inspectCpuProfile(callback: () => T): T; - -export function inspectCpuProfile(callback: () => any) { - return withCpuProfile(callback, (profile) => { - pprof.encode(profile).then((buffer) => { - const filename = Path.join(Os.tmpdir(), Date.now() + '.pb.gz'); - Fs.writeFile(filename, buffer, (err) => { - execa('pprof', ['-web', filename]); - }); - }); - }); -} diff --git a/packages/kbn-adhoc-profiler/kibana.jsonc b/packages/kbn-adhoc-profiler/kibana.jsonc deleted file mode 100644 index bae020a4b1e8d..0000000000000 --- a/packages/kbn-adhoc-profiler/kibana.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/adhoc-profiler", - "owner": "@elastic/apm-ui", - "runtimeDeps": [], - "typeDeps": [], -} diff --git a/packages/kbn-adhoc-profiler/with_cpu_profile.ts b/packages/kbn-adhoc-profiler/with_cpu_profile.ts deleted file mode 100644 index 25833f8cb4805..0000000000000 --- a/packages/kbn-adhoc-profiler/with_cpu_profile.ts +++ /dev/null @@ -1,32 +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 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 or the Server - * Side Public License, v 1. - */ -import { isPromise } from 'util/types'; -import { pprof } from './require_pprof'; -import { Profile } from './types'; - -export function withCpuProfile(callback: () => T, onProfileDone: (profile: Profile) => void): T; - -export function withCpuProfile(callback: () => any, onProfileDone: (profile: Profile) => void) { - const stop = pprof.time.start(); - - const result = callback(); - - function collectProfile() { - const profile = stop(); - onProfileDone(profile); - } - - if (isPromise(result)) { - result.finally(() => { - collectProfile(); - }); - } else { - collectProfile(); - } - return result; -} diff --git a/packages/kbn-alerts/BUILD.bazel b/packages/kbn-alerts/BUILD.bazel index 86a23118abfe0..c9e52274d39f5 100644 --- a/packages/kbn-alerts/BUILD.bazel +++ b/packages/kbn-alerts/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-alerts" +PKG_DIRNAME = "kbn-alerts" PKG_REQUIRE_NAME = "@kbn/alerts" SOURCE_FILES = glob( @@ -89,7 +89,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -98,9 +98,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-analytics/BUILD.bazel b/packages/kbn-analytics/BUILD.bazel index 4c9065c836068..53d0cbf16ddee 100644 --- a/packages/kbn-analytics/BUILD.bazel +++ b/packages/kbn-analytics/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-analytics" +PKG_DIRNAME = "kbn-analytics" PKG_REQUIRE_NAME = "@kbn/analytics" SOURCE_FILES = glob( @@ -92,7 +92,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -101,9 +101,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-apm-config-loader/BUILD.bazel b/packages/kbn-apm-config-loader/BUILD.bazel index 8d83162d204bc..526193788cd92 100644 --- a/packages/kbn-apm-config-loader/BUILD.bazel +++ b/packages/kbn-apm-config-loader/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-apm-config-loader" +PKG_DIRNAME = "kbn-apm-config-loader" PKG_REQUIRE_NAME = "@kbn/apm-config-loader" SOURCE_FILES = glob( @@ -82,7 +82,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -91,9 +91,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-apm-config-loader/index.ts b/packages/kbn-apm-config-loader/index.ts index e2027b2e12c7c..24f46c01b4b3f 100644 --- a/packages/kbn-apm-config-loader/index.ts +++ b/packages/kbn-apm-config-loader/index.ts @@ -10,3 +10,4 @@ export { getConfiguration } from './src/config_loader'; export { initApm } from './src/init_apm'; export { shouldInstrumentClient } from './src/rum_agent_configuration'; export type { ApmConfiguration } from './src/config'; +export { apmConfigSchema } from './src/apm_config'; diff --git a/packages/kbn-apm-config-loader/src/apm_config.ts b/packages/kbn-apm-config-loader/src/apm_config.ts new file mode 100644 index 0000000000000..2127d612d583b --- /dev/null +++ b/packages/kbn-apm-config-loader/src/apm_config.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 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 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; + +export const apmConfigSchema = schema.object( + { + active: schema.maybe(schema.boolean()), + serverUrl: schema.maybe(schema.uri()), + secretToken: schema.maybe(schema.string()), + globalLabels: schema.object({}, { unknowns: 'allow' }), + }, + { unknowns: 'allow' } +); diff --git a/packages/kbn-apm-synthtrace/BUILD.bazel b/packages/kbn-apm-synthtrace/BUILD.bazel index 2f87b86044915..4107a948e27c8 100644 --- a/packages/kbn-apm-synthtrace/BUILD.bazel +++ b/packages/kbn-apm-synthtrace/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-apm-synthtrace" +PKG_DIRNAME = "kbn-apm-synthtrace" PKG_REQUIRE_NAME = "@kbn/apm-synthtrace" SOURCE_FILES = glob( @@ -61,6 +61,7 @@ TYPES_DEPS = [ "@npm//p-limit", "@npm//@types/node-fetch", "@npm//@types/semver", + "@npm//@types/yargs", ] jsts_transpiler( @@ -92,7 +93,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -101,9 +102,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/serverless.ts b/packages/kbn-apm-synthtrace/src/lib/apm/serverless.ts index b67586c18a074..f2eb47685bf39 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/serverless.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/serverless.ts @@ -33,7 +33,6 @@ export class Serverless extends BaseSpan { ...fields, 'metricset.name': 'app', 'faas.execution': faasExection, - 'faas.id': fields['service.name'], }); } diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/serverless_function.ts b/packages/kbn-apm-synthtrace/src/lib/apm/serverless_function.ts index e10bb23b1f933..c8287066cc273 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/serverless_function.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/serverless_function.ts @@ -7,7 +7,6 @@ */ import { Entity } from '../entity'; -import { generateShortId } from '../utils/generate_id'; import { ApmFields } from './apm_fields'; import { ServerlessInstance } from './serverless_instance'; @@ -33,7 +32,7 @@ export function serverlessFunction({ agentName: string; serviceName?: string; }) { - const faasId = `arn:aws:lambda:us-west-2:${generateShortId()}:function:${functionName}`; + const faasId = `arn:aws:lambda:us-west-2:001:function:${functionName}`; return new ServerlessFunction({ 'service.name': serviceName || faasId, 'faas.id': faasId, diff --git a/packages/kbn-apm-utils/BUILD.bazel b/packages/kbn-apm-utils/BUILD.bazel index ab3004dc47d36..41b28d8c11cfc 100644 --- a/packages/kbn-apm-utils/BUILD.bazel +++ b/packages/kbn-apm-utils/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-apm-utils" +PKG_DIRNAME = "kbn-apm-utils" PKG_REQUIRE_NAME = "@kbn/apm-utils" SOURCE_FILES = glob( @@ -72,7 +72,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -81,9 +81,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-babel-plugin-synthetic-packages/BUILD.bazel b/packages/kbn-babel-plugin-synthetic-packages/BUILD.bazel index da588dab2ce42..a1e6891f23ec5 100644 --- a/packages/kbn-babel-plugin-synthetic-packages/BUILD.bazel +++ b/packages/kbn-babel-plugin-synthetic-packages/BUILD.bazel @@ -44,9 +44,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_DIRNAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-babel-preset/BUILD.bazel b/packages/kbn-babel-preset/BUILD.bazel index 54dc3bafd8ac8..7b4090ceac48e 100644 --- a/packages/kbn-babel-preset/BUILD.bazel +++ b/packages/kbn-babel-preset/BUILD.bazel @@ -1,7 +1,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "pkg_npm") -PKG_BASE_NAME = "kbn-babel-preset" +PKG_DIRNAME = "kbn-babel-preset" PKG_REQUIRE_NAME = "@kbn/babel-preset" SOURCE_FILES = glob([ @@ -43,7 +43,7 @@ RUNTIME_DEPS = [ ] js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ], @@ -54,9 +54,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-cli-dev-mode/BUILD.bazel b/packages/kbn-cli-dev-mode/BUILD.bazel index 9b03aaa956b54..90d11fdeb62bb 100644 --- a/packages/kbn-cli-dev-mode/BUILD.bazel +++ b/packages/kbn-cli-dev-mode/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-cli-dev-mode" +PKG_DIRNAME = "kbn-cli-dev-mode" PKG_REQUIRE_NAME = "@kbn/cli-dev-mode" SOURCE_FILES = glob( @@ -110,7 +110,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -119,9 +119,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.test.tsx b/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.test.tsx index 696baceffb540..f7f8367d9ed7b 100644 --- a/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.test.tsx +++ b/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.test.tsx @@ -61,7 +61,7 @@ describe('palette panel', () => { dataBounds: { min: 0, max: 100 }, }; - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); function changePaletteIn(instance: ReactWrapper, newPaletteName: string) { diff --git a/packages/kbn-config-schema/BUILD.bazel b/packages/kbn-config-schema/BUILD.bazel index aebd34efa2f08..c14ba00345437 100644 --- a/packages/kbn-config-schema/BUILD.bazel +++ b/packages/kbn-config-schema/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-config-schema" +PKG_DIRNAME = "kbn-config-schema" PKG_REQUIRE_NAME = "@kbn/config-schema" SOURCE_FILES = glob( @@ -82,7 +82,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -91,9 +91,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-config/BUILD.bazel b/packages/kbn-config/BUILD.bazel index eef3d336b57dd..4e1066bd7a19b 100644 --- a/packages/kbn-config/BUILD.bazel +++ b/packages/kbn-config/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-config" +PKG_DIRNAME = "kbn-config" PKG_REQUIRE_NAME = "@kbn/config" SOURCE_FILES = glob( @@ -101,7 +101,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -110,9 +110,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-crypto/BUILD.bazel b/packages/kbn-crypto/BUILD.bazel index 4dade7bba6ca2..55ed47a64303a 100644 --- a/packages/kbn-crypto/BUILD.bazel +++ b/packages/kbn-crypto/BUILD.bazel @@ -3,7 +3,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-crypto" +PKG_DIRNAME = "kbn-crypto" PKG_REQUIRE_NAME = "@kbn/crypto" SOURCE_FILES = glob( @@ -79,7 +79,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -88,9 +88,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-datemath/BUILD.bazel b/packages/kbn-datemath/BUILD.bazel index ae4e19b9b7c8c..95e93f70e92e1 100644 --- a/packages/kbn-datemath/BUILD.bazel +++ b/packages/kbn-datemath/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "ts_project", "pkg_npm", "pkg_npm_types") -PKG_BASE_NAME = "kbn-datemath" +PKG_DIRNAME = "kbn-datemath" PKG_REQUIRE_NAME = "@kbn/datemath" SOURCE_FILES = glob( @@ -71,7 +71,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -80,9 +80,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts b/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts index 329e858b08f5e..150765f1d9b6a 100644 --- a/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts +++ b/packages/kbn-dev-cli-runner/src/run_with_commands.test.ts @@ -8,6 +8,7 @@ import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log'; import { ProcRunner } from '@kbn/dev-proc-runner'; +jest.mock('./metrics'); import { FlagsReader } from './flags_reader'; import { RunWithCommands } from './run_with_commands'; @@ -48,7 +49,7 @@ it('extends the context using extendContext()', async () => { flagsReader: expect.any(FlagsReader), addCleanupTask: expect.any(Function), procRunner: expect.any(ProcRunner), - statsMeta: expect.any(Map), + statsMeta: undefined, extraContext: true, }); diff --git a/packages/kbn-dev-utils/BUILD.bazel b/packages/kbn-dev-utils/BUILD.bazel index 42543650a1051..849ea8404f32c 100644 --- a/packages/kbn-dev-utils/BUILD.bazel +++ b/packages/kbn-dev-utils/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-dev-utils" +PKG_DIRNAME = "kbn-dev-utils" PKG_REQUIRE_NAME = "@kbn/dev-utils" SOURCE_FILES = glob( @@ -153,7 +153,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -162,9 +162,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-dev-utils/src/diff_strings.ts b/packages/kbn-dev-utils/src/diff_strings.ts index 11b7e574c7560..03ca506fd4c90 100644 --- a/packages/kbn-dev-utils/src/diff_strings.ts +++ b/packages/kbn-dev-utils/src/diff_strings.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import jestDiff from 'jest-diff'; +import { diff as jestDiff } from 'jest-diff'; import stripAnsi from 'strip-ansi'; import Chalk from 'chalk'; diff --git a/packages/kbn-doc-links/BUILD.bazel b/packages/kbn-doc-links/BUILD.bazel index c4849085c0ae2..292560832da85 100644 --- a/packages/kbn-doc-links/BUILD.bazel +++ b/packages/kbn-doc-links/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-doc-links" +PKG_DIRNAME = "kbn-doc-links" PKG_REQUIRE_NAME = "@kbn/doc-links" SOURCE_FILES = glob( @@ -82,7 +82,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -91,9 +91,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 8783f21a1af9f..796e46ecbf6e5 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -50,6 +50,10 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { overview: `${APM_DOCS}guide/${DOC_LINK_VERSION}/apm-overview.html`, tailSamplingPolicies: `${APM_DOCS}guide/${DOC_LINK_VERSION}/configure-tail-based-sampling.html`, elasticAgent: `${APM_DOCS}guide/${DOC_LINK_VERSION}/upgrade-to-apm-integration.html`, + storageExplorer: `${KIBANA_DOCS}storage-explorer.html`, + spanCompression: `${APM_DOCS}guide/${DOC_LINK_VERSION}/span-compression.html`, + transactionSampling: `${APM_DOCS}guide/${DOC_LINK_VERSION}/sampling.html`, + indexLifecycleManagement: `${APM_DOCS}guide/${DOC_LINK_VERSION}/ilm-how-to.html`, }, canvas: { guide: `${KIBANA_DOCS}canvas.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 443de50276691..7affe129b8173 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -35,6 +35,10 @@ export interface DocLinks { readonly overview: string; readonly tailSamplingPolicies: string; readonly elasticAgent: string; + readonly storageExplorer: string; + readonly spanCompression: string; + readonly transactionSampling: string; + readonly indexLifecycleManagement: string; }; readonly canvas: { readonly guide: string; diff --git a/packages/kbn-docs-utils/BUILD.bazel b/packages/kbn-docs-utils/BUILD.bazel index 33687685100e1..5564b87e15f13 100644 --- a/packages/kbn-docs-utils/BUILD.bazel +++ b/packages/kbn-docs-utils/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-docs-utils" +PKG_DIRNAME = "kbn-docs-utils" PKG_REQUIRE_NAME = "@kbn/docs-utils" SOURCE_FILES = glob( @@ -86,7 +86,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -95,9 +95,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-ebt-tools/BUILD.bazel b/packages/kbn-ebt-tools/BUILD.bazel index ca3591e936703..5b318045b9301 100644 --- a/packages/kbn-ebt-tools/BUILD.bazel +++ b/packages/kbn-ebt-tools/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-ebt-tools" +PKG_DIRNAME = "kbn-ebt-tools" PKG_REQUIRE_NAME = "@kbn/ebt-tools" SOURCE_FILES = glob( @@ -72,7 +72,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], package_name = PKG_REQUIRE_NAME, @@ -81,9 +81,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-es-archiver/BUILD.bazel b/packages/kbn-es-archiver/BUILD.bazel index 7b0f9f0346630..7d214d913aeae 100644 --- a/packages/kbn-es-archiver/BUILD.bazel +++ b/packages/kbn-es-archiver/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-es-archiver" +PKG_DIRNAME = "kbn-es-archiver" PKG_REQUIRE_NAME = "@kbn/es-archiver" SOURCE_FILES = glob( @@ -94,7 +94,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -103,9 +103,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-es-query/BUILD.bazel b/packages/kbn-es-query/BUILD.bazel index 2a7c445c32aac..a34b58155359d 100644 --- a/packages/kbn-es-query/BUILD.bazel +++ b/packages/kbn-es-query/BUILD.bazel @@ -3,7 +3,7 @@ load("@npm//peggy:index.bzl", "peggy") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-es-query" +PKG_DIRNAME = "kbn-es-query" PKG_REQUIRE_NAME = "@kbn/es-query" SOURCE_FILES = glob( @@ -109,7 +109,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [":grammar"], deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -118,9 +118,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-es-query/src/kuery/functions/is.test.ts b/packages/kbn-es-query/src/kuery/functions/is.test.ts index f36ea0d17dc8c..bde7e956f0c43 100644 --- a/packages/kbn-es-query/src/kuery/functions/is.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/is.test.ts @@ -361,6 +361,23 @@ describe('kuery functions', () => { expect(result).toEqual(expected); }); + + test('should use a term query for keyword fields', () => { + const node = nodeTypes.function.buildNode('is', 'machine.os.keyword', 'Win 7'); + const result = is.toElasticsearchQuery(node, indexPattern); + expect(result).toEqual({ + bool: { + should: [ + { + term: { + 'machine.os.keyword': 'Win 7', + }, + }, + ], + minimum_should_match: 1, + }, + }); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/is.ts b/packages/kbn-es-query/src/kuery/functions/is.ts index f2db74857b02e..5493a3a6072e6 100644 --- a/packages/kbn-es-query/src/kuery/functions/is.ts +++ b/packages/kbn-es-query/src/kuery/functions/is.ts @@ -100,6 +100,7 @@ export function toElasticsearchQuery( } const queries = fields!.reduce((accumulator: any, field: DataViewFieldBase) => { + const isKeywordField = field.esTypes?.length === 1 && field.esTypes.includes('keyword'); const wrapWithNestedQuery = (query: any) => { // Wildcards can easily include nested and non-nested fields. There isn't a good way to let // users handle this themselves so we automatically add nested queries in this scenario. @@ -142,7 +143,7 @@ export function toElasticsearchQuery( }), ]; } else if (wildcard.isNode(valueArg)) { - const query = field.esTypes?.includes('keyword') + const query = isKeywordField ? { wildcard: { [field.name]: value, @@ -177,7 +178,7 @@ export function toElasticsearchQuery( }), ]; } else { - const queryType = type === 'phrase' ? 'match_phrase' : 'match'; + const queryType = isKeywordField ? 'term' : type === 'phrase' ? 'match_phrase' : 'match'; return [ ...accumulator, wrapWithNestedQuery({ diff --git a/packages/kbn-es/BUILD.bazel b/packages/kbn-es/BUILD.bazel index 23ddda10f36e5..2aeaee4071d53 100644 --- a/packages/kbn-es/BUILD.bazel +++ b/packages/kbn-es/BUILD.bazel @@ -1,7 +1,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm") -PKG_BASE_NAME = "kbn-es" +PKG_DIRNAME = "kbn-es" PKG_REQUIRE_NAME = "@kbn/es" SOURCE_FILES = glob( @@ -57,7 +57,7 @@ jsts_transpiler( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -66,9 +66,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-eslint-config/BUILD.bazel b/packages/kbn-eslint-config/BUILD.bazel index 73f834f7d5f63..708136256498d 100644 --- a/packages/kbn-eslint-config/BUILD.bazel +++ b/packages/kbn-eslint-config/BUILD.bazel @@ -1,7 +1,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "pkg_npm") -PKG_BASE_NAME = "kbn-eslint-config" +PKG_DIRNAME = "kbn-eslint-config" PKG_REQUIRE_NAME = "@kbn/eslint-config" SOURCE_FILES = glob([ @@ -34,7 +34,7 @@ RUNTIME_DEPS = [ ] js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ], @@ -45,9 +45,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-eslint-plugin-eslint/BUILD.bazel b/packages/kbn-eslint-plugin-eslint/BUILD.bazel index c043c4e468db9..0bb2ff549c9f9 100644 --- a/packages/kbn-eslint-plugin-eslint/BUILD.bazel +++ b/packages/kbn-eslint-plugin-eslint/BUILD.bazel @@ -1,7 +1,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "pkg_npm") -PKG_BASE_NAME = "kbn-eslint-plugin-eslint" +PKG_DIRNAME = "kbn-eslint-plugin-eslint" PKG_REQUIRE_NAME = "@kbn/eslint-plugin-eslint" SOURCE_FILES = glob( @@ -46,7 +46,7 @@ RUNTIME_DEPS = [ ] js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ], @@ -57,9 +57,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-expect/BUILD.bazel b/packages/kbn-expect/BUILD.bazel index 415b402a3d05c..70ed34ad091ce 100644 --- a/packages/kbn-expect/BUILD.bazel +++ b/packages/kbn-expect/BUILD.bazel @@ -1,7 +1,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "pkg_npm") -PKG_BASE_NAME = "kbn-expect" +PKG_DIRNAME = "kbn-expect" PKG_REQUIRE_NAME = "@kbn/expect" SOURCE_FILES = glob([ @@ -22,7 +22,7 @@ NPM_MODULE_EXTRA_FILES = [ ] js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ], @@ -32,9 +32,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-field-types/BUILD.bazel b/packages/kbn-field-types/BUILD.bazel index 1d5ca19241bba..fa6bf48c39c88 100644 --- a/packages/kbn-field-types/BUILD.bazel +++ b/packages/kbn-field-types/BUILD.bazel @@ -3,7 +3,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library",) load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-field-types" +PKG_DIRNAME = "kbn-field-types" PKG_REQUIRE_NAME = "@kbn/field-types" SOURCE_FILES = glob( @@ -87,7 +87,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -96,9 +96,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-flot-charts/BUILD.bazel b/packages/kbn-flot-charts/BUILD.bazel index d819fa05c7d16..ec2655bc2bbf1 100644 --- a/packages/kbn-flot-charts/BUILD.bazel +++ b/packages/kbn-flot-charts/BUILD.bazel @@ -1,7 +1,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "pkg_npm") -PKG_BASE_NAME = "kbn-flot-charts" +PKG_DIRNAME = "kbn-flot-charts" PKG_REQUIRE_NAME = "@kbn/flot-charts" SOURCE_FILES = glob([ @@ -26,7 +26,7 @@ RUNTIME_DEPS = [ ] js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ], @@ -37,9 +37,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-generate/BUILD.bazel b/packages/kbn-generate/BUILD.bazel index 098291d8a0877..e4afaec6069b9 100644 --- a/packages/kbn-generate/BUILD.bazel +++ b/packages/kbn-generate/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-generate" +PKG_DIRNAME = "kbn-generate" PKG_REQUIRE_NAME = "@kbn/generate" SOURCE_FILES = glob( @@ -85,7 +85,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -94,9 +94,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card_footer.test.tsx.snap b/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card_footer.test.tsx.snap index 9b18465e91be9..cbb21df42a054 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card_footer.test.tsx.snap +++ b/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card_footer.test.tsx.snap @@ -44,6 +44,19 @@ exports[`guide card footer snapshots should render the footer when the guide is
    diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx index ef9373996297c..574b9b18bf2b3 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx +++ b/packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx @@ -9,7 +9,6 @@ import React, { ReactNode } from 'react'; import { EuiCard, EuiText, EuiImage } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { GuideId } from '../../types'; type UseCaseConstants = { [key in UseCase]: { @@ -53,7 +52,7 @@ const constants: UseCaseConstants = { export type UseCase = 'search' | 'observability' | 'security'; export interface UseCaseCardProps { - useCase: GuideId; + useCase: UseCase; title: string; description: string; footer: ReactNode; @@ -84,18 +83,13 @@ export const UseCaseCard = ({ ); - const descriptionElement = ( - -

    {description}

    -
    - ); + return ( } title={titleElement} - description={descriptionElement} + description={description} footer={footer} betaBadgeProps={{ label: constants[useCase].betaBadgeLabel, diff --git a/packages/kbn-guided-onboarding/src/types.ts b/packages/kbn-guided-onboarding/src/types.ts index 9a307464cefb8..6b919835da2e7 100644 --- a/packages/kbn-guided-onboarding/src/types.ts +++ b/packages/kbn-guided-onboarding/src/types.ts @@ -6,13 +6,14 @@ * Side Public License, v 1. */ -export type GuideId = 'observability' | 'security' | 'search'; +export type GuideId = 'observability' | 'security' | 'search' | 'testGuide'; -export type ObservabilityStepIds = 'add_data' | 'view_dashboard' | 'tour_observability'; -export type SecurityStepIds = 'add_data' | 'rules' | 'alertsCases'; -export type SearchStepIds = 'add_data' | 'browse_docs' | 'search_experience'; +type ObservabilityStepIds = 'add_data' | 'view_dashboard' | 'tour_observability'; +type SecurityStepIds = 'add_data' | 'rules' | 'alertsCases'; +type SearchStepIds = 'add_data' | 'browse_docs' | 'search_experience'; +type TestGuideIds = 'step1' | 'step2' | 'step3'; -export type GuideStepIds = ObservabilityStepIds | SecurityStepIds | SearchStepIds; +export type GuideStepIds = ObservabilityStepIds | SecurityStepIds | SearchStepIds | TestGuideIds; export interface GuideState { guideId: GuideId; diff --git a/packages/kbn-handlebars/BUILD.bazel b/packages/kbn-handlebars/BUILD.bazel index 4e3d283a0be8e..984366123bafb 100644 --- a/packages/kbn-handlebars/BUILD.bazel +++ b/packages/kbn-handlebars/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-handlebars" +PKG_DIRNAME = "kbn-handlebars" PKG_REQUIRE_NAME = "@kbn/handlebars" TYPES_PKG_REQUIRE_NAME = "@types/kbn__handlebars" @@ -84,7 +84,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -93,9 +93,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-i18n-react/BUILD.bazel b/packages/kbn-i18n-react/BUILD.bazel index efba43ef1cf34..cfcf823bec4a8 100644 --- a/packages/kbn-i18n-react/BUILD.bazel +++ b/packages/kbn-i18n-react/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-i18n-react" +PKG_DIRNAME = "kbn-i18n-react" PKG_REQUIRE_NAME = "@kbn/i18n-react" SOURCE_FILES = glob( @@ -89,7 +89,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -98,9 +98,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-i18n/BUILD.bazel b/packages/kbn-i18n/BUILD.bazel index 90079be18cab2..d58fdfc60df1e 100644 --- a/packages/kbn-i18n/BUILD.bazel +++ b/packages/kbn-i18n/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-i18n" +PKG_DIRNAME = "kbn-i18n" PKG_REQUIRE_NAME = "@kbn/i18n" SOURCE_FILES = glob( @@ -89,7 +89,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -98,9 +98,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-interpreter/BUILD.bazel b/packages/kbn-interpreter/BUILD.bazel index d711715d519c8..e2cd2103ddde9 100644 --- a/packages/kbn-interpreter/BUILD.bazel +++ b/packages/kbn-interpreter/BUILD.bazel @@ -3,7 +3,7 @@ load("@npm//peggy:index.bzl", "peggy") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-interpreter" +PKG_DIRNAME = "kbn-interpreter" PKG_REQUIRE_NAME = "@kbn/interpreter" SOURCE_FILES = glob( @@ -101,7 +101,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [":grammar"], deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -110,9 +110,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-io-ts-utils/BUILD.bazel b/packages/kbn-io-ts-utils/BUILD.bazel index 48641035223e5..322c44f18a5b8 100644 --- a/packages/kbn-io-ts-utils/BUILD.bazel +++ b/packages/kbn-io-ts-utils/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-io-ts-utils" +PKG_DIRNAME = "kbn-io-ts-utils" PKG_REQUIRE_NAME = "@kbn/io-ts-utils" SOURCE_FILES = glob( @@ -88,7 +88,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -97,9 +97,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-language-documentation-popover/BUILD.bazel b/packages/kbn-language-documentation-popover/BUILD.bazel index d596368bd91ee..2e2eaa3760abb 100644 --- a/packages/kbn-language-documentation-popover/BUILD.bazel +++ b/packages/kbn-language-documentation-popover/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-language-documentation-popover" +PKG_DIRNAME = "kbn-language-documentation-popover" PKG_REQUIRE_NAME = "@kbn/language-documentation-popover" SOURCE_FILES = glob( @@ -101,7 +101,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_webpack"], package_name = PKG_REQUIRE_NAME, @@ -110,9 +110,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-language-documentation-popover/index.ts b/packages/kbn-language-documentation-popover/index.ts index 89d1f238c06d0..2c1746383917a 100644 --- a/packages/kbn-language-documentation-popover/index.ts +++ b/packages/kbn-language-documentation-popover/index.ts @@ -7,4 +7,5 @@ */ export { LanguageDocumentationPopover } from './src/components/documentation_popover'; +export { LanguageDocumentationPopoverContent } from './src/components/documentation_content'; export type { LanguageDocumentationSections } from './src/components/documentation_content'; diff --git a/packages/kbn-language-documentation-popover/src/components/documentation.scss b/packages/kbn-language-documentation-popover/src/components/documentation.scss index 4cf5fbd08cdcd..752797decfa4e 100644 --- a/packages/kbn-language-documentation-popover/src/components/documentation.scss +++ b/packages/kbn-language-documentation-popover/src/components/documentation.scss @@ -1,80 +1,11 @@ -.documentation { - display: flex; - flex-direction: column; - - & > * { - flex: 1; - min-height: 0; - } - - & > * + * { - border-top: $euiBorderThin; - } -} - -.documentation__editor { - - & > * + * { - border-top: $euiBorderThin; - } -} - -.documentation__editorHeader, -.documentation__editorFooter { - padding: $euiSizeS $euiSize; -} - -.documentation__editorFooter { - // make sure docs are rendered in front of monaco - z-index: 1; - background-color: $euiColorLightestShade; -} - -.documentation__editorHeaderGroup, -.documentation__editorFooterGroup { - display: block; // Overrides EUI's styling of `display: flex` on `EuiFlexItem` components -} - -.documentation__editorContent { - min-height: 0; - position: relative; -} - -.documentation__editorPlaceholder { - position: absolute; - top: 0; - left: $euiSize; - right: 0; - color: $euiTextSubduedColor; - // Matches monaco editor - font-family: Menlo, Monaco, 'Courier New', monospace; - pointer-events: none; -} - -.documentation__warningText + .documentation__warningText { - margin-top: $euiSizeS; - border-top: $euiBorderThin; - padding-top: $euiSizeS; -} - -.documentation__editorHelp--inline { - align-items: center; - display: flex; - padding: $euiSizeXS; - - & > * + * { - margin-left: $euiSizeXS; - } -} - -.documentation__editorError { - white-space: nowrap; -} - .documentation__docs { background: $euiColorEmptyShade; } +.documentation__docsHeader { + margin: 0; +} + .documentation__docs--inline { display: flex; flex-direction: column; diff --git a/packages/kbn-language-documentation-popover/src/components/documentation_content.test.tsx b/packages/kbn-language-documentation-popover/src/components/documentation_content.test.tsx index 6c36e1a5e54d0..6d91cc403795e 100644 --- a/packages/kbn-language-documentation-popover/src/components/documentation_content.test.tsx +++ b/packages/kbn-language-documentation-popover/src/components/documentation_content.test.tsx @@ -38,7 +38,7 @@ describe('###Documentation popover content', () => { test('Documentation component has a header element referring to the language given', () => { const component = mountWithIntl(); const title = findTestSubject(component, 'language-documentation-title'); - expect(title.text()).toEqual('TEST reference'); + expect(title.text()).toEqual('test reference'); }); test('Documentation component has a sidebar navigation list with all the section labels', () => { diff --git a/packages/kbn-language-documentation-popover/src/components/documentation_content.tsx b/packages/kbn-language-documentation-popover/src/components/documentation_content.tsx index c5470aeea6fd1..b7c2e800bbaf5 100644 --- a/packages/kbn-language-documentation-popover/src/components/documentation_content.tsx +++ b/packages/kbn-language-documentation-popover/src/components/documentation_content.tsx @@ -76,7 +76,7 @@ function DocumentationContent({ language, sections }: DocumentationProps) { > {i18n.translate('languageDocumentationPopover.header', { defaultMessage: '{language} reference', - values: { language: language.toUpperCase() }, + values: { language }, })} setIsHelpOpen(false)} - ownFocus={false} button={ diff --git a/packages/kbn-logging-mocks/BUILD.bazel b/packages/kbn-logging-mocks/BUILD.bazel index 1af99a6a62954..78d175af69dec 100644 --- a/packages/kbn-logging-mocks/BUILD.bazel +++ b/packages/kbn-logging-mocks/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-logging-mocks" +PKG_DIRNAME = "kbn-logging-mocks" PKG_REQUIRE_NAME = "@kbn/logging-mocks" SOURCE_FILES = glob( @@ -72,7 +72,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -81,9 +81,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-logging/BUILD.bazel b/packages/kbn-logging/BUILD.bazel index 4d7668cc09650..cf64d4247f328 100644 --- a/packages/kbn-logging/BUILD.bazel +++ b/packages/kbn-logging/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-logging" +PKG_DIRNAME = "kbn-logging" PKG_REQUIRE_NAME = "@kbn/logging" SOURCE_FILES = glob( @@ -74,7 +74,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -83,9 +83,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-managed-vscode-config/src/update_vscode_config.test.ts b/packages/kbn-managed-vscode-config/src/update_vscode_config.test.ts index f9e3aecb89b93..cfa1964dd297b 100644 --- a/packages/kbn-managed-vscode-config/src/update_vscode_config.test.ts +++ b/packages/kbn-managed-vscode-config/src/update_vscode_config.test.ts @@ -133,7 +133,6 @@ it('persists comments in the original file', () => { `); expect(newJson).toMatchInlineSnapshot(` // @managed - /** * This is a top level comment */ diff --git a/packages/kbn-mapbox-gl/BUILD.bazel b/packages/kbn-mapbox-gl/BUILD.bazel index a55f5f77897d7..e81aa132d1111 100644 --- a/packages/kbn-mapbox-gl/BUILD.bazel +++ b/packages/kbn-mapbox-gl/BUILD.bazel @@ -3,7 +3,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-mapbox-gl" +PKG_DIRNAME = "kbn-mapbox-gl" PKG_REQUIRE_NAME = "@kbn/mapbox-gl" SOURCE_FILES = glob( @@ -84,7 +84,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -93,9 +93,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-monaco/BUILD.bazel b/packages/kbn-monaco/BUILD.bazel index 2ed705e1c81c3..dbf1b3f0af065 100644 --- a/packages/kbn-monaco/BUILD.bazel +++ b/packages/kbn-monaco/BUILD.bazel @@ -3,7 +3,7 @@ load("@npm//webpack-cli:index.bzl", webpack = "webpack_cli") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-monaco" +PKG_DIRNAME = "kbn-monaco" PKG_REQUIRE_NAME = "@kbn/monaco" SOURCE_FILES = glob( @@ -113,7 +113,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web", ":target_workers"], package_name = PKG_REQUIRE_NAME, @@ -122,9 +122,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts b/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts index 5d00ad726d031..7af335ed46987 100644 --- a/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts +++ b/packages/kbn-monaco/src/painless/diagnostics_adapter.test.ts @@ -24,9 +24,8 @@ const getMockWorker = async () => { } as any; }; -function flushPromises() { - return new Promise((resolve) => setImmediate(resolve)); -} +const flushPromises = () => + new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); describe('Painless DiagnosticAdapter', () => { let diagnosticAdapter: DiagnosticsAdapter; @@ -35,7 +34,7 @@ describe('Painless DiagnosticAdapter', () => { let validation: LangValidation; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/packages/kbn-optimizer/BUILD.bazel b/packages/kbn-optimizer/BUILD.bazel index 0701fa141dd9a..530058c9f5d7e 100644 --- a/packages/kbn-optimizer/BUILD.bazel +++ b/packages/kbn-optimizer/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-optimizer" +PKG_DIRNAME = "kbn-optimizer" PKG_REQUIRE_NAME = "@kbn/optimizer" SOURCE_FILES = glob( @@ -133,7 +133,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -142,9 +142,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts b/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts index 694a606883957..c280890f2dff3 100644 --- a/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts +++ b/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts @@ -11,7 +11,7 @@ import { fakeSchedulers } from 'rxjs-marbles/jest'; import { pipeClosure, debounceTimeBuffer, maybeMap, maybe } from './rxjs_helpers'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); describe('pipeClosure()', () => { it('calls closure on each subscription to setup unique state', async () => { @@ -70,7 +70,7 @@ describe('maybeMap()', () => { describe('debounceTimeBuffer()', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { diff --git a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts b/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts index 91a2f7324806a..08f6cdc61bce3 100644 --- a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts @@ -58,7 +58,7 @@ const bundleCacheEvent$ = Rx.from(BUNDLES).pipe( ); beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(async () => { diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts b/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts index 1cfc1b184d87a..d22ba95282002 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts @@ -23,7 +23,7 @@ jest.mock('@kbn/synthetic-package-map', () => { jest.mock('../common/hashes', () => { return { Hashes: class MockHashes { - static ofFiles = jest.fn(() => { + static ofFiles: any = jest.fn(() => { return new MockHashes(); }); diff --git a/packages/kbn-plugin-generator/BUILD.bazel b/packages/kbn-plugin-generator/BUILD.bazel index 587cd546d50fb..d3ad237231c25 100644 --- a/packages/kbn-plugin-generator/BUILD.bazel +++ b/packages/kbn-plugin-generator/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-plugin-generator" +PKG_DIRNAME = "kbn-plugin-generator" PKG_REQUIRE_NAME = "@kbn/plugin-generator" SOURCE_FILES = glob( @@ -102,7 +102,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -111,9 +111,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-plugin-helpers/BUILD.bazel b/packages/kbn-plugin-helpers/BUILD.bazel index 32f8f0a3720db..c1793269c2fee 100644 --- a/packages/kbn-plugin-helpers/BUILD.bazel +++ b/packages/kbn-plugin-helpers/BUILD.bazel @@ -3,7 +3,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-plugin-helpers" +PKG_DIRNAME = "kbn-plugin-helpers" PKG_REQUIRE_NAME = "@kbn/plugin-helpers" SOURCE_FILES = glob( @@ -95,7 +95,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -104,9 +104,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-react-field/BUILD.bazel b/packages/kbn-react-field/BUILD.bazel index ccc12245d323f..0a851c9a156cf 100644 --- a/packages/kbn-react-field/BUILD.bazel +++ b/packages/kbn-react-field/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-react-field" +PKG_DIRNAME = "kbn-react-field" PKG_REQUIRE_NAME = "@kbn/react-field" SOURCE_FILES = glob( @@ -51,8 +51,8 @@ TYPES_DEPS = [ "@npm//@elastic/eui", "@npm//@types/classnames", "@npm//@types/jest", - "@npm//@types/prop-types", "@npm//@types/node", + "@npm//@types/prop-types", "@npm//@types/react", ] @@ -99,7 +99,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_webpack"], package_name = PKG_REQUIRE_NAME, @@ -108,9 +108,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-rule-data-utils/BUILD.bazel b/packages/kbn-rule-data-utils/BUILD.bazel index 61146f0b5de31..788ef54533536 100644 --- a/packages/kbn-rule-data-utils/BUILD.bazel +++ b/packages/kbn-rule-data-utils/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-rule-data-utils" +PKG_DIRNAME = "kbn-rule-data-utils" PKG_REQUIRE_NAME = "@kbn/rule-data-utils" SOURCE_FILES = glob( @@ -86,7 +86,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -95,9 +95,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-safer-lodash-set/BUILD.bazel b/packages/kbn-safer-lodash-set/BUILD.bazel index 893719c822859..3a5d07ab38904 100644 --- a/packages/kbn-safer-lodash-set/BUILD.bazel +++ b/packages/kbn-safer-lodash-set/BUILD.bazel @@ -1,6 +1,6 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -PKG_BASE_NAME = "kbn-safer-lodash-set" +PKG_DIRNAME = "kbn-safer-lodash-set" PKG_REQUIRE_NAME = "@kbn/safer-lodash-set" SOURCE_FILES = glob( @@ -40,7 +40,7 @@ DEPS = [ ] js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ], @@ -51,9 +51,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( @@ -66,7 +64,7 @@ filegroup( alias( name = "npm_module_types", - actual = PKG_BASE_NAME, + actual = PKG_DIRNAME, visibility = ["//visibility:public"], ) diff --git a/packages/kbn-securitysolution-autocomplete/BUILD.bazel b/packages/kbn-securitysolution-autocomplete/BUILD.bazel index d09b4cf04c70a..ae396cfb7a18d 100644 --- a/packages/kbn-securitysolution-autocomplete/BUILD.bazel +++ b/packages/kbn-securitysolution-autocomplete/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-autocomplete" +PKG_DIRNAME = "kbn-securitysolution-autocomplete" PKG_REQUIRE_NAME = "@kbn/securitysolution-autocomplete" SOURCE_FILES = glob( @@ -109,7 +109,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -118,9 +118,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-es-utils/BUILD.bazel b/packages/kbn-securitysolution-es-utils/BUILD.bazel index 6eb3a84e83fce..59dbdb1fa63a6 100644 --- a/packages/kbn-securitysolution-es-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-es-utils/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-es-utils" +PKG_DIRNAME = "kbn-securitysolution-es-utils" PKG_REQUIRE_NAME = "@kbn/securitysolution-es-utils" SOURCE_FILES = glob( @@ -80,7 +80,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -89,9 +89,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ], + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-hook-utils/BUILD.bazel b/packages/kbn-securitysolution-hook-utils/BUILD.bazel index 6959bfcc69534..ab33e32a0ad4c 100644 --- a/packages/kbn-securitysolution-hook-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-hook-utils/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-hook-utils" +PKG_DIRNAME = "kbn-securitysolution-hook-utils" PKG_REQUIRE_NAME = "@kbn/securitysolution-hook-utils" SOURCE_FILES = glob( @@ -87,7 +87,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -96,9 +96,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ], + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/BUILD.bazel b/packages/kbn-securitysolution-io-ts-alerting-types/BUILD.bazel index 7ddd3ab480bfc..713f56917c19f 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/BUILD.bazel +++ b/packages/kbn-securitysolution-io-ts-alerting-types/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-io-ts-alerting-types" +PKG_DIRNAME = "kbn-securitysolution-io-ts-alerting-types" PKG_REQUIRE_NAME = "@kbn/securitysolution-io-ts-alerting-types" SOURCE_FILES = glob( @@ -90,7 +90,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -99,9 +99,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/index.ts index 75d5352c9f63e..7a200e4f4c8f9 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/index.ts @@ -20,7 +20,6 @@ export * from './src/default_severity_mapping_array'; export * from './src/default_threat_array'; export * from './src/default_to_string'; export * from './src/default_uuid'; -export * from './src/from'; export * from './src/language'; export * from './src/machine_learning_job_id'; export * from './src/max_signals'; @@ -28,6 +27,7 @@ export * from './src/normalized_ml_job_id'; export * from './src/references_default_array'; export * from './src/risk_score'; export * from './src/risk_score_mapping'; +export * from './src/rule_schedule'; export * from './src/saved_object_attributes'; export * from './src/severity'; export * from './src/severity_mapping'; diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts index 023af9fc7050e..2e0d814bf93c5 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/actions/index.ts @@ -6,42 +6,47 @@ * Side Public License, v 1. */ -/* eslint-disable @typescript-eslint/naming-convention */ - import * as t from 'io-ts'; import { saved_object_attributes } from '../saved_object_attributes'; +export type RuleActionGroup = t.TypeOf; +export const RuleActionGroup = t.string; + +export type RuleActionId = t.TypeOf; +export const RuleActionId = t.string; + +export type RuleActionTypeId = t.TypeOf; +export const RuleActionTypeId = t.string; + /** * Params is an "object", since it is a type of RuleActionParams which is action templates. * @see x-pack/plugins/alerting/common/rule.ts */ -export const action_group = t.string; -export const action_id = t.string; -export const action_action_type_id = t.string; -export const action_params = saved_object_attributes; +export type RuleActionParams = t.TypeOf; +export const RuleActionParams = saved_object_attributes; -export const action = t.exact( +export type RuleAction = t.TypeOf; +export const RuleAction = t.exact( t.type({ - group: action_group, - id: action_id, - action_type_id: action_action_type_id, - params: action_params, + group: RuleActionGroup, + id: RuleActionId, + action_type_id: RuleActionTypeId, + params: RuleActionParams, }) ); -export type Action = t.TypeOf; +export type RuleActionArray = t.TypeOf; +export const RuleActionArray = t.array(RuleAction); -export const actions = t.array(action); -export type Actions = t.TypeOf; - -export const actionsCamel = t.array( - t.exact( - t.type({ - group: action_group, - id: action_id, - actionTypeId: action_action_type_id, - params: action_params, - }) - ) +export type RuleActionCamel = t.TypeOf; +export const RuleActionCamel = t.exact( + t.type({ + group: RuleActionGroup, + id: RuleActionId, + actionTypeId: RuleActionTypeId, + params: RuleActionParams, + }) ); -export type ActionsCamel = t.TypeOf; + +export type RuleActionArrayCamel = t.TypeOf; +export const RuleActionArrayCamel = t.array(RuleActionCamel); diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_actions_array/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/default_actions_array/index.ts index 9d741aa65e079..be90cdc0816ef 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_actions_array/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/default_actions_array/index.ts @@ -8,12 +8,16 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; -import { actions, Actions } from '../actions'; +import { RuleActionArray } from '../actions'; -export const DefaultActionsArray = new t.Type( +export const DefaultActionsArray = new t.Type< + RuleActionArray, + RuleActionArray | undefined, + unknown +>( 'DefaultActionsArray', - actions.is, - (input, context): Either => - input == null ? t.success([]) : actions.validate(input, context), + RuleActionArray.is, + (input, context): Either => + input == null ? t.success([]) : RuleActionArray.validate(input, context), t.identity ); diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_from_string/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/default_from_string/index.ts index 55b76ab7c1a4e..d91499ee36089 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_from_string/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/default_from_string/index.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; -import { from } from '../from'; +import { From } from '../from'; /** * Types the DefaultFromString as: @@ -21,7 +21,7 @@ export const DefaultFromString = new t.Type if (input == null) { return t.success('now-6m'); } - return from.validate(input, context); + return From.validate(input, context); }, t.identity ); diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_risk_score_mapping_array/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/default_risk_score_mapping_array/index.ts index 8bd913af9255b..6d2314f684f1c 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_risk_score_mapping_array/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/default_risk_score_mapping_array/index.ts @@ -8,11 +8,11 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; -import { RiskScoreMapping, risk_score_mapping } from '../risk_score_mapping'; +import { RiskScoreMapping } from '../risk_score_mapping'; /** * Types the DefaultStringArray as: - * - If null or undefined, then a default risk_score_mapping array will be set + * - If null or undefined, then a default RiskScoreMapping array will be set */ export const DefaultRiskScoreMappingArray = new t.Type< RiskScoreMapping, @@ -20,8 +20,8 @@ export const DefaultRiskScoreMappingArray = new t.Type< unknown >( 'DefaultRiskScoreMappingArray', - risk_score_mapping.is, + RiskScoreMapping.is, (input, context): Either => - input == null ? t.success([]) : risk_score_mapping.validate(input, context), + input == null ? t.success([]) : RiskScoreMapping.validate(input, context), t.identity ); diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_severity_mapping_array/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/default_severity_mapping_array/index.ts index 58a96eef5a14f..b8e37e45c35f9 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/default_severity_mapping_array/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/default_severity_mapping_array/index.ts @@ -8,11 +8,11 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; -import { SeverityMapping, severity_mapping } from '../severity_mapping'; +import { SeverityMapping } from '../severity_mapping'; /** * Types the DefaultStringArray as: - * - If null or undefined, then a default severity_mapping array will be set + * - If null or undefined, then a default SeverityMapping array will be set */ export const DefaultSeverityMappingArray = new t.Type< SeverityMapping, @@ -20,8 +20,8 @@ export const DefaultSeverityMappingArray = new t.Type< unknown >( 'DefaultSeverityMappingArray', - severity_mapping.is, + SeverityMapping.is, (input, context): Either => - input == null ? t.success([]) : severity_mapping.validate(input, context), + input == null ? t.success([]) : SeverityMapping.validate(input, context), t.identity ); diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/from/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/from/index.ts index 30b3c727d87a2..97747696b90a3 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/from/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/from/index.ts @@ -12,7 +12,8 @@ import { parseScheduleDates } from '@kbn/securitysolution-io-ts-utils'; const stringValidator = (input: unknown): input is string => typeof input === 'string'; -export const from = new t.Type( +export type From = t.TypeOf; +export const From = new t.Type( 'From', t.string.is, (input, context): Either => { @@ -23,7 +24,3 @@ export const from = new t.Type( }, t.identity ); -export type From = t.TypeOf; - -export const fromOrUndefined = t.union([from, t.undefined]); -export type FromOrUndefined = t.TypeOf; diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts index 98b9c33e7e3ea..c4f301d6a7c5d 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score/index.ts @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -/* eslint-disable @typescript-eslint/naming-convention */ - import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; @@ -16,6 +14,7 @@ import { Either } from 'fp-ts/lib/Either'; * - Natural Number (positive integer and not a float), * - Between the values [0 and 100] inclusive. */ +export type RiskScore = t.TypeOf; export const RiskScore = new t.Type( 'RiskScore', t.number.is, @@ -26,11 +25,3 @@ export const RiskScore = new t.Type( }, t.identity ); - -export type RiskScoreC = typeof RiskScore; - -export const risk_score = RiskScore; -export type RiskScore = t.TypeOf; - -export const riskScoreOrUndefined = t.union([risk_score, t.undefined]); -export type RiskScoreOrUndefined = t.TypeOf; diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts index be07bab64f469..5b56e85cf4e8b 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/risk_score_mapping/index.ts @@ -6,25 +6,19 @@ * Side Public License, v 1. */ -/* eslint-disable @typescript-eslint/naming-convention */ - import * as t from 'io-ts'; import { operator } from '@kbn/securitysolution-io-ts-types'; -import { riskScoreOrUndefined } from '../risk_score'; +import { RiskScore } from '../risk_score'; -export const risk_score_mapping_field = t.string; -export const risk_score_mapping_value = t.string; -export const risk_score_mapping_item = t.exact( +export type RiskScoreMappingItem = t.TypeOf; +export const RiskScoreMappingItem = t.exact( t.type({ - field: risk_score_mapping_field, - value: risk_score_mapping_value, + field: t.string, + value: t.string, operator, - risk_score: riskScoreOrUndefined, + risk_score: t.union([RiskScore, t.undefined]), }) ); -export const risk_score_mapping = t.array(risk_score_mapping_item); -export type RiskScoreMapping = t.TypeOf; - -export const riskScoreMappingOrUndefined = t.union([risk_score_mapping, t.undefined]); -export type RiskScoreMappingOrUndefined = t.TypeOf; +export type RiskScoreMapping = t.TypeOf; +export const RiskScoreMapping = t.array(RiskScoreMappingItem); diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/rule_schedule/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/rule_schedule/index.ts new file mode 100644 index 0000000000000..63fdfafe5631c --- /dev/null +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/rule_schedule/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 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 or the Server + * Side Public License, v 1. + */ + +import * as t from 'io-ts'; +import { From } from '../from'; + +export type RuleInterval = t.TypeOf; +export const RuleInterval = t.string; // we need a more specific schema + +export type RuleIntervalFrom = t.TypeOf; +export const RuleIntervalFrom = From; + +/** + * TODO: Create a regular expression type or custom date math part type here + */ +export type RuleIntervalTo = t.TypeOf; +export const RuleIntervalTo = t.string; // we need a more specific schema diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/severity/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/severity/index.ts index 4caafa6b6ecb2..19a35b56e05b4 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/severity/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/severity/index.ts @@ -8,8 +8,5 @@ import * as t from 'io-ts'; -export const severity = t.keyof({ low: null, medium: null, high: null, critical: null }); -export type Severity = t.TypeOf; - -export const severityOrUndefined = t.union([severity, t.undefined]); -export type SeverityOrUndefined = t.TypeOf; +export type Severity = t.TypeOf; +export const Severity = t.keyof({ low: null, medium: null, high: null, critical: null }); diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/severity_mapping/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/severity_mapping/index.ts index 1a3fd50039c29..203f862426c37 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/severity_mapping/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/severity_mapping/index.ts @@ -6,27 +6,20 @@ * Side Public License, v 1. */ -/* eslint-disable @typescript-eslint/naming-convention */ - import * as t from 'io-ts'; import { operator } from '@kbn/securitysolution-io-ts-types'; -import { severity } from '../severity'; +import { Severity } from '../severity'; -export const severity_mapping_field = t.string; -export const severity_mapping_value = t.string; -export const severity_mapping_item = t.exact( +export type SeverityMappingItem = t.TypeOf; +export const SeverityMappingItem = t.exact( t.type({ - field: severity_mapping_field, + field: t.string, operator, - value: severity_mapping_value, - severity, + value: t.string, + severity: Severity, }) ); -export type SeverityMappingItem = t.TypeOf; - -export const severity_mapping = t.array(severity_mapping_item); -export type SeverityMapping = t.TypeOf; -export const severityMappingOrUndefined = t.union([severity_mapping, t.undefined]); -export type SeverityMappingOrUndefined = t.TypeOf; +export type SeverityMapping = t.TypeOf; +export const SeverityMapping = t.array(SeverityMappingItem); diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts b/packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts index d7d636ad0994e..fbc75ca67693e 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts +++ b/packages/kbn-securitysolution-io-ts-alerting-types/src/throttle/index.ts @@ -6,15 +6,12 @@ * Side Public License, v 1. */ -import { TimeDuration } from '@kbn/securitysolution-io-ts-types'; import * as t from 'io-ts'; +import { TimeDuration } from '@kbn/securitysolution-io-ts-types'; -export const throttle = t.union([ +export type RuleActionThrottle = t.TypeOf; +export const RuleActionThrottle = t.union([ t.literal('no_actions'), t.literal('rule'), TimeDuration({ allowedUnits: ['h', 'd'] }), ]); -export type Throttle = t.TypeOf; - -export const throttleOrNull = t.union([throttle, t.null]); -export type ThrottleOrNull = t.TypeOf; diff --git a/packages/kbn-securitysolution-io-ts-list-types/BUILD.bazel b/packages/kbn-securitysolution-io-ts-list-types/BUILD.bazel index 7a4388b65053e..718ab4e75c9d8 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/BUILD.bazel +++ b/packages/kbn-securitysolution-io-ts-list-types/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-io-ts-list-types" +PKG_DIRNAME = "kbn-securitysolution-io-ts-list-types" PKG_REQUIRE_NAME = "@kbn/securitysolution-io-ts-list-types" SOURCE_FILES = glob( @@ -91,7 +91,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -100,9 +100,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-io-ts-types/BUILD.bazel b/packages/kbn-securitysolution-io-ts-types/BUILD.bazel index 85f5a05ed6c44..f09e4139fccca 100644 --- a/packages/kbn-securitysolution-io-ts-types/BUILD.bazel +++ b/packages/kbn-securitysolution-io-ts-types/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-io-ts-types" +PKG_DIRNAME = "kbn-securitysolution-io-ts-types" PKG_REQUIRE_NAME = "@kbn/securitysolution-io-ts-types" SOURCE_FILES = glob( @@ -88,7 +88,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -97,9 +97,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-io-ts-utils/BUILD.bazel b/packages/kbn-securitysolution-io-ts-utils/BUILD.bazel index 6e8d4de3ccfde..eb30bfe8cc433 100644 --- a/packages/kbn-securitysolution-io-ts-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-io-ts-utils/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-io-ts-utils" +PKG_DIRNAME = "kbn-securitysolution-io-ts-utils" PKG_REQUIRE_NAME = "@kbn/securitysolution-io-ts-utils" SOURCE_FILES = glob( @@ -91,7 +91,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -100,9 +100,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-list-api/BUILD.bazel b/packages/kbn-securitysolution-list-api/BUILD.bazel index 9819e867e3194..39f3f797c569b 100644 --- a/packages/kbn-securitysolution-list-api/BUILD.bazel +++ b/packages/kbn-securitysolution-list-api/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-list-api" +PKG_DIRNAME = "kbn-securitysolution-list-api" PKG_REQUIRE_NAME = "@kbn/securitysolution-list-api" SOURCE_FILES = glob( @@ -90,7 +90,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -99,9 +99,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ], + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-list-constants/BUILD.bazel b/packages/kbn-securitysolution-list-constants/BUILD.bazel index 5d4a6c2ffb117..779eef5617de1 100644 --- a/packages/kbn-securitysolution-list-constants/BUILD.bazel +++ b/packages/kbn-securitysolution-list-constants/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-list-constants" +PKG_DIRNAME = "kbn-securitysolution-list-constants" PKG_REQUIRE_NAME = "@kbn/securitysolution-list-constants" SOURCE_FILES = glob( @@ -78,7 +78,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -87,9 +87,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ], + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-list-hooks/BUILD.bazel b/packages/kbn-securitysolution-list-hooks/BUILD.bazel index 93a3d0c683dd5..2487cf359d29e 100644 --- a/packages/kbn-securitysolution-list-hooks/BUILD.bazel +++ b/packages/kbn-securitysolution-list-hooks/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-list-hooks" +PKG_DIRNAME = "kbn-securitysolution-list-hooks" PKG_REQUIRE_NAME = "@kbn/securitysolution-list-hooks" SOURCE_FILES = glob( @@ -97,7 +97,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -106,9 +106,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ], + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-list-utils/BUILD.bazel b/packages/kbn-securitysolution-list-utils/BUILD.bazel index d1d1a9a3e326a..5155da63bfbc5 100644 --- a/packages/kbn-securitysolution-list-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-list-utils/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-list-utils" +PKG_DIRNAME = "kbn-securitysolution-list-utils" PKG_REQUIRE_NAME = "@kbn/securitysolution-list-utils" SOURCE_FILES = glob( @@ -97,7 +97,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -107,9 +107,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ], + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-rules/BUILD.bazel b/packages/kbn-securitysolution-rules/BUILD.bazel index e1da8501dffa1..280c7cd0dae50 100644 --- a/packages/kbn-securitysolution-rules/BUILD.bazel +++ b/packages/kbn-securitysolution-rules/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-rules" +PKG_DIRNAME = "kbn-securitysolution-rules" PKG_REQUIRE_NAME = "@kbn/securitysolution-rules" SOURCE_FILES = glob( @@ -85,7 +85,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -94,9 +94,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ], + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-t-grid/BUILD.bazel b/packages/kbn-securitysolution-t-grid/BUILD.bazel index 39c1aff010f88..d907afc660311 100644 --- a/packages/kbn-securitysolution-t-grid/BUILD.bazel +++ b/packages/kbn-securitysolution-t-grid/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-t-grid" +PKG_DIRNAME = "kbn-securitysolution-t-grid" PKG_REQUIRE_NAME = "@kbn/securitysolution-t-grid" SOURCE_FILES = glob( @@ -86,7 +86,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -95,9 +95,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ], + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-securitysolution-utils/BUILD.bazel b/packages/kbn-securitysolution-utils/BUILD.bazel index a76553e2fb2c5..68e9ab6dd597b 100644 --- a/packages/kbn-securitysolution-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-utils/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-securitysolution-utils" +PKG_DIRNAME = "kbn-securitysolution-utils" PKG_REQUIRE_NAME = "@kbn/securitysolution-utils" SOURCE_FILES = glob( @@ -85,7 +85,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -94,9 +94,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ], + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-server-http-tools/BUILD.bazel b/packages/kbn-server-http-tools/BUILD.bazel index 5c73a34ffe5e7..004cfb336f049 100644 --- a/packages/kbn-server-http-tools/BUILD.bazel +++ b/packages/kbn-server-http-tools/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-server-http-tools" +PKG_DIRNAME = "kbn-server-http-tools" PKG_REQUIRE_NAME = "@kbn/server-http-tools" SOURCE_FILES = glob( @@ -89,7 +89,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -98,9 +98,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-server-route-repository/BUILD.bazel b/packages/kbn-server-route-repository/BUILD.bazel index cbe6172e32e40..7ecc92bbe1a26 100644 --- a/packages/kbn-server-route-repository/BUILD.bazel +++ b/packages/kbn-server-route-repository/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-server-route-repository" +PKG_DIRNAME = "kbn-server-route-repository" PKG_REQUIRE_NAME = "@kbn/server-route-repository" SOURCE_FILES = glob( @@ -96,7 +96,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -105,9 +105,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-spec-to-console/BUILD.bazel b/packages/kbn-spec-to-console/BUILD.bazel index ee046c6194f7f..9d41b5762d470 100644 --- a/packages/kbn-spec-to-console/BUILD.bazel +++ b/packages/kbn-spec-to-console/BUILD.bazel @@ -1,7 +1,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "pkg_npm") -PKG_BASE_NAME = "kbn-spec-to-console" +PKG_DIRNAME = "kbn-spec-to-console" PKG_REQUIRE_NAME = "@kbn/spec-to-console" SOURCE_FILES = glob( @@ -33,7 +33,7 @@ RUNTIME_DEPS = [ ] js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ], @@ -44,9 +44,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-std/BUILD.bazel b/packages/kbn-std/BUILD.bazel index fb59d13596ab7..f92779194187f 100644 --- a/packages/kbn-std/BUILD.bazel +++ b/packages/kbn-std/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-std" +PKG_DIRNAME = "kbn-std" PKG_REQUIRE_NAME = "@kbn/std" SOURCE_FILES = glob( @@ -83,7 +83,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -92,9 +92,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-storybook/BUILD.bazel b/packages/kbn-storybook/BUILD.bazel index c85352b8ac914..e58a4954fd44c 100644 --- a/packages/kbn-storybook/BUILD.bazel +++ b/packages/kbn-storybook/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-storybook" +PKG_DIRNAME = "kbn-storybook" PKG_REQUIRE_NAME = "@kbn/storybook" SOURCE_FILES = glob( @@ -115,7 +115,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -124,9 +124,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-telemetry-tools/BUILD.bazel b/packages/kbn-telemetry-tools/BUILD.bazel index b234f2dd6ea45..d4e2a87075782 100644 --- a/packages/kbn-telemetry-tools/BUILD.bazel +++ b/packages/kbn-telemetry-tools/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-telemetry-tools" +PKG_DIRNAME = "kbn-telemetry-tools" PKG_REQUIRE_NAME = "@kbn/telemetry-tools" SOURCE_FILES = glob( @@ -89,7 +89,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -98,9 +98,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-test-jest-helpers/BUILD.bazel b/packages/kbn-test-jest-helpers/BUILD.bazel index d31f9184cf2ac..6017936b06552 100644 --- a/packages/kbn-test-jest-helpers/BUILD.bazel +++ b/packages/kbn-test-jest-helpers/BUILD.bazel @@ -3,7 +3,7 @@ load("@npm//@babel/cli:index.bzl", "babel") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-test-jest-helpers" +PKG_DIRNAME = "kbn-test-jest-helpers" PKG_REQUIRE_NAME = "@kbn/test-jest-helpers" SOURCE_FILES = glob( @@ -148,7 +148,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -157,9 +157,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-test/BUILD.bazel b/packages/kbn-test/BUILD.bazel index 1deca3a0f6d07..32eccf2963060 100644 --- a/packages/kbn-test/BUILD.bazel +++ b/packages/kbn-test/BUILD.bazel @@ -3,7 +3,7 @@ load("@npm//@babel/cli:index.bzl", "babel") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-test" +PKG_DIRNAME = "kbn-test" PKG_REQUIRE_NAME = "@kbn/test" SOURCE_FILES = glob( @@ -170,7 +170,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -179,9 +179,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index 3cb0ce86602cd..c13dbb5149f43 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -113,9 +113,6 @@ module.exports = { `integration_tests/`, ], - // This option allows use of a custom test runner - testRunner: 'jest-circus/runner', - // A map from regular expressions to paths to transformers transform: { '^.+\\.(js|tsx?)$': '/node_modules/@kbn/test/target_node/src/jest/babel_transform.js', diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts index 02e96f62e2488..b7efb33bcd506 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts @@ -13,8 +13,6 @@ import { addSerializer, } from 'jest-snapshot'; import path from 'path'; -import prettier from 'prettier'; -import babelTraverse from '@babel/traverse'; import { once } from 'lodash'; import { Lifecycle } from '../lifecycle'; import { Suite, Test } from '../../fake_mocha_types'; @@ -161,8 +159,8 @@ function getSnapshotState(file: string, updateSnapshot: SnapshotUpdateState) { path.join(dirname + `/__snapshots__/` + filename.replace(path.extname(filename), '.snap')), { updateSnapshot, - getPrettier: () => prettier, - getBabelTraverse: () => babelTraverse, + prettierPath: require.resolve('prettier'), + snapshotFormat: { escapeString: true, printBasicPrototype: true }, } ); diff --git a/packages/kbn-test/src/jest/babel_transform.js b/packages/kbn-test/src/jest/babel_transform.js index 8b25b635bbb13..f2fbbfe00b603 100644 --- a/packages/kbn-test/src/jest/babel_transform.js +++ b/packages/kbn-test/src/jest/babel_transform.js @@ -8,7 +8,7 @@ const babelJest = require('babel-jest'); -module.exports = babelJest.createTransformer({ +module.exports = babelJest.default.createTransformer({ presets: [ [ require.resolve('@kbn/babel-preset/node_preset'), diff --git a/packages/kbn-test/src/jest/configs/get_tests_for_config_paths.ts b/packages/kbn-test/src/jest/configs/get_tests_for_config_paths.ts index 9d0105977a12e..10474413ba297 100644 --- a/packages/kbn-test/src/jest/configs/get_tests_for_config_paths.ts +++ b/packages/kbn-test/src/jest/configs/get_tests_for_config_paths.ts @@ -7,8 +7,8 @@ */ import { readConfig } from 'jest-config'; -import { createContext } from 'jest-runtime'; import { SearchSource } from 'jest'; +import Runtime from 'jest-runtime'; import { asyncMapWithLimit } from '@kbn/std'; const EMPTY_ARGV = { @@ -34,7 +34,7 @@ export async function getTestsForConfigPaths( return await asyncMapWithLimit(configPaths, 60, async (path) => { const config = await readConfig(EMPTY_ARGV, path); const searchSource = new SearchSource( - await createContext(config.projectConfig, { + await Runtime.createContext(config.projectConfig, { maxWorkers: 1, watchman: false, watch: false, diff --git a/packages/kbn-test/src/jest/junit_reporter.ts b/packages/kbn-test/src/jest/junit_reporter.ts index eb5828120a57b..6a1ce9d51ded9 100644 --- a/packages/kbn-test/src/jest/junit_reporter.ts +++ b/packages/kbn-test/src/jest/junit_reporter.ts @@ -60,8 +60,9 @@ export default class JestJUnitReporter extends BaseReporter { ); const msToIso = (ms: number | null | undefined) => - ms ? new Date(ms).toISOString().slice(0, -5) : undefined; - const msToSec = (ms: number | null | undefined) => (ms ? (ms / 1000).toFixed(3) : undefined); + typeof ms === 'number' ? new Date(ms).toISOString().slice(0, -5) : undefined; + const msToSec = (ms: number | null | undefined) => + typeof ms === 'number' ? (ms / 1000).toFixed(3) : undefined; root.att({ name: 'jest', diff --git a/packages/kbn-test/src/jest/run.ts b/packages/kbn-test/src/jest/run.ts index daf4706cebdba..8efcafbec6c87 100644 --- a/packages/kbn-test/src/jest/run.ts +++ b/packages/kbn-test/src/jest/run.ts @@ -20,31 +20,18 @@ import { resolve, relative, sep as osSep } from 'path'; import { existsSync } from 'fs'; import { run } from 'jest'; -import { buildArgv } from 'jest-cli/build/cli'; import { ToolingLog } from '@kbn/tooling-log'; import { getTimeReporter } from '@kbn/ci-stats-reporter'; import { REPO_ROOT } from '@kbn/utils'; import { map } from 'lodash'; +import getopts from 'getopts'; // yarn test:jest src/core/server/saved_objects // yarn test:jest src/core/public/core_system.test.ts // :kibana/src/core/server/saved_objects yarn test:jest -// Patch node 16 types to be compatible with jest 26 -// https://github.com/facebook/jest/issues/11640#issuecomment-893867514 -/* eslint-disable @typescript-eslint/no-namespace,@typescript-eslint/no-empty-interface,no-console */ -declare global { - namespace NodeJS { - interface Global {} - interface InspectOptions {} - - interface ConsoleConstructor extends console.ConsoleConstructor {} - } -} -/* eslint-enable */ - export function runJest(configName = 'jest.config.js') { - const argv = buildArgv(process.argv); + const argv = getopts(process.argv.slice(2)); const devConfigName = 'jest.config.dev.js'; const log = new ToolingLog({ @@ -60,7 +47,7 @@ export function runJest(configName = 'jest.config.js') { const cwd: string = process.env.INIT_CWD || process.cwd(); if (!argv.config) { - testFiles = argv._.splice(2).map((p) => resolve(cwd, p.toString())); + testFiles = argv._.map((p) => resolve(cwd, p.toString())); const commonTestFiles = commonBasePath(testFiles); const testFilesProvided = testFiles.length > 0; diff --git a/packages/kbn-test/src/jest/setup/setup_test.js b/packages/kbn-test/src/jest/setup/setup_test.js index a3eb15a2d2634..b0038daf196c9 100644 --- a/packages/kbn-test/src/jest/setup/setup_test.js +++ b/packages/kbn-test/src/jest/setup/setup_test.js @@ -13,3 +13,9 @@ import 'jest-styled-components'; import '@testing-library/jest-dom'; + +/** + * Removed in Jest 27/jsdom, used in some transitive dependencies + */ +global.setImmediate = require('core-js/stable/set-immediate'); +global.clearImmediate = require('core-js/stable/clear-immediate'); diff --git a/packages/kbn-timelion-grammar/BUILD.bazel b/packages/kbn-timelion-grammar/BUILD.bazel index c906223c2253b..3c7ea13cadf64 100644 --- a/packages/kbn-timelion-grammar/BUILD.bazel +++ b/packages/kbn-timelion-grammar/BUILD.bazel @@ -2,7 +2,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("@npm//peggy:index.bzl", "peggy") load("//src/dev/bazel:index.bzl", "pkg_npm") -PKG_BASE_NAME = "kbn-timelion-grammar" +PKG_DIRNAME = "kbn-timelion-grammar" PKG_REQUIRE_NAME = "@kbn/timelion-grammar" NPM_MODULE_EXTRA_FILES = [ @@ -23,7 +23,7 @@ peggy( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [ ":grammar" ], @@ -33,9 +33,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-tinymath/BUILD.bazel b/packages/kbn-tinymath/BUILD.bazel index ffbd74da2d02e..b8ee25a4973b8 100644 --- a/packages/kbn-tinymath/BUILD.bazel +++ b/packages/kbn-tinymath/BUILD.bazel @@ -2,7 +2,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("@npm//peggy:index.bzl", "peggy") load("//src/dev/bazel:index.bzl", "pkg_npm") -PKG_BASE_NAME = "kbn-tinymath" +PKG_DIRNAME = "kbn-tinymath" PKG_REQUIRE_NAME = "@kbn/tinymath" SOURCE_FILES = glob( @@ -45,7 +45,7 @@ peggy( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ":grammar" @@ -57,9 +57,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-typed-react-router-config/BUILD.bazel b/packages/kbn-typed-react-router-config/BUILD.bazel index c14f2b66b9870..e6f1587e537ed 100644 --- a/packages/kbn-typed-react-router-config/BUILD.bazel +++ b/packages/kbn-typed-react-router-config/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-typed-react-router-config" +PKG_DIRNAME = "kbn-typed-react-router-config" PKG_REQUIRE_NAME = "@kbn/typed-react-router-config" SOURCE_FILES = glob( @@ -100,7 +100,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -109,9 +109,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-ui-framework/BUILD.bazel b/packages/kbn-ui-framework/BUILD.bazel index f38e10eafeec7..2e801955a8524 100644 --- a/packages/kbn-ui-framework/BUILD.bazel +++ b/packages/kbn-ui-framework/BUILD.bazel @@ -1,7 +1,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "pkg_npm") -PKG_BASE_NAME = "kbn-ui-framework" +PKG_DIRNAME = "kbn-ui-framework" PKG_REQUIRE_NAME = "@kbn/ui-framework" SOURCE_FILES = glob([ @@ -23,7 +23,7 @@ NPM_MODULE_EXTRA_FILES = [ RUNTIME_DEPS = [] js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ], @@ -34,9 +34,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-ui-shared-deps-npm/BUILD.bazel b/packages/kbn-ui-shared-deps-npm/BUILD.bazel index c20613e128e49..7f589c7c0a842 100644 --- a/packages/kbn-ui-shared-deps-npm/BUILD.bazel +++ b/packages/kbn-ui-shared-deps-npm/BUILD.bazel @@ -3,7 +3,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("@npm//webpack-cli:index.bzl", webpack = "webpack_cli") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-ui-shared-deps-npm" +PKG_DIRNAME = "kbn-ui-shared-deps-npm" PKG_REQUIRE_NAME = "@kbn/ui-shared-deps-npm" SOURCE_FILES = glob( @@ -154,7 +154,7 @@ webpack( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":shared_built_assets"], package_name = PKG_REQUIRE_NAME, @@ -163,9 +163,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-ui-shared-deps-src/BUILD.bazel b/packages/kbn-ui-shared-deps-src/BUILD.bazel index 4979792e00ee5..0507f18756929 100644 --- a/packages/kbn-ui-shared-deps-src/BUILD.bazel +++ b/packages/kbn-ui-shared-deps-src/BUILD.bazel @@ -3,7 +3,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("@npm//webpack-cli:index.bzl", webpack = "webpack_cli") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-ui-shared-deps-src" +PKG_DIRNAME = "kbn-ui-shared-deps-src" PKG_REQUIRE_NAME = "@kbn/ui-shared-deps-src" SOURCE_FILES = glob( @@ -111,7 +111,7 @@ webpack( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":shared_built_assets"], package_name = PKG_REQUIRE_NAME, @@ -120,9 +120,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-ui-theme/BUILD.bazel b/packages/kbn-ui-theme/BUILD.bazel index a004975293a0b..0a890d07fba0f 100644 --- a/packages/kbn-ui-theme/BUILD.bazel +++ b/packages/kbn-ui-theme/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-ui-theme" +PKG_DIRNAME = "kbn-ui-theme" PKG_REQUIRE_NAME = "@kbn/ui-theme" SOURCE_FILES = glob( @@ -80,7 +80,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -89,9 +89,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-user-profile-components/BUILD.bazel b/packages/kbn-user-profile-components/BUILD.bazel index fff27d0f68c34..1037d47a79ad4 100644 --- a/packages/kbn-user-profile-components/BUILD.bazel +++ b/packages/kbn-user-profile-components/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-user-profile-components" +PKG_DIRNAME = "kbn-user-profile-components" PKG_REQUIRE_NAME = "@kbn/user-profile-components" SOURCE_FILES = glob( @@ -87,7 +87,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":target_web"], package_name = PKG_REQUIRE_NAME, @@ -96,9 +96,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-utility-types-jest/BUILD.bazel b/packages/kbn-utility-types-jest/BUILD.bazel index 3b8985767b244..589d17734e55a 100644 --- a/packages/kbn-utility-types-jest/BUILD.bazel +++ b/packages/kbn-utility-types-jest/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-utility-types-jest" +PKG_DIRNAME = "kbn-utility-types-jest" PKG_REQUIRE_NAME = "@kbn/utility-types-jest" SOURCE_FILES = glob( @@ -70,7 +70,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -79,9 +79,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-utility-types/BUILD.bazel b/packages/kbn-utility-types/BUILD.bazel index ad463d9342580..20c640a4b2250 100644 --- a/packages/kbn-utility-types/BUILD.bazel +++ b/packages/kbn-utility-types/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-utility-types" +PKG_DIRNAME = "kbn-utility-types" PKG_REQUIRE_NAME = "@kbn/utility-types" SOURCE_FILES = glob( @@ -74,7 +74,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, @@ -83,9 +83,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/kbn-utils/BUILD.bazel b/packages/kbn-utils/BUILD.bazel index b488b66303910..857ff523a3269 100644 --- a/packages/kbn-utils/BUILD.bazel +++ b/packages/kbn-utils/BUILD.bazel @@ -2,7 +2,7 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-utils" +PKG_DIRNAME = "kbn-utils" PKG_REQUIRE_NAME = "@kbn/utils" SOURCE_FILES = glob( @@ -78,7 +78,7 @@ ts_project( ) js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], package_name = PKG_REQUIRE_NAME, @@ -87,9 +87,7 @@ js_library( pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ] + deps = [":" + PKG_DIRNAME], ) filegroup( diff --git a/packages/shared-ux/storybook/config/BUILD.bazel b/packages/shared-ux/storybook/config/BUILD.bazel index 32e5b6cb440c7..422fe45ee7226 100644 --- a/packages/shared-ux/storybook/config/BUILD.bazel +++ b/packages/shared-ux/storybook/config/BUILD.bazel @@ -62,12 +62,12 @@ RUNTIME_DEPS = [ # # References to NPM packages work the same as RUNTIME_DEPS TYPES_DEPS = [ - "@npm//jest-mock", "//packages/kbn-storybook:npm_module_types", "//packages/kbn-ambient-ui-types:npm_module_types", "//packages/kbn-ambient-storybook-types:npm_module_types", "@npm//@types/node", "@npm//@types/jest", + "@npm//jest-mock", "@npm//@storybook/react", "@npm//@storybook/addon-actions", ] diff --git a/src/core/server/core_app/core_app.test.ts b/src/core/server/core_app/core_app.test.ts index 1ea3eeef29a09..31e4b6176a889 100644 --- a/src/core/server/core_app/core_app.test.ts +++ b/src/core/server/core_app/core_app.test.ts @@ -13,7 +13,7 @@ import { mockRouter } from '@kbn/core-http-router-server-mocks'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; import { coreMock, httpServerMock } from '../mocks'; import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; -import { PluginType } from '../plugins'; +import { PluginType } from '@kbn/core-base-common'; import { CoreApp } from './core_app'; import { RequestHandlerContext } from '..'; diff --git a/src/core/server/core_app/core_app.ts b/src/core/server/core_app/core_app.ts index f0940f6abad50..83665512767e7 100644 --- a/src/core/server/core_app/core_app.ts +++ b/src/core/server/core_app/core_app.ts @@ -22,7 +22,7 @@ import type { } from '@kbn/core-http-server'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; import type { HttpResources, HttpResourcesServiceToolkit } from '@kbn/core-http-resources-server'; -import { InternalCorePreboot, InternalCoreSetup } from '../internal_types'; +import { InternalCorePreboot, InternalCoreSetup } from '@kbn/core-lifecycle-server-internal'; import { registerBundleRoutes } from './bundle_routes'; import type { InternalCoreAppRequestHandlerContext } from './internal_types'; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 6232a17eb6111..df770c8529ab8 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -29,14 +29,8 @@ */ import { Type } from '@kbn/config-schema'; -import type { DocLinksServiceStart, DocLinksServiceSetup } from '@kbn/core-doc-links-server'; -import type { AppenderConfigType, LoggingServiceSetup } from '@kbn/core-logging-server'; +import type { AppenderConfigType } from '@kbn/core-logging-server'; import { appendersSchema } from '@kbn/core-logging-server-internal'; -import type { - AnalyticsServiceSetup, - AnalyticsServiceStart, - AnalyticsServicePreboot, -} from '@kbn/core-analytics-server'; import type { ExecutionContextSetup, ExecutionContextStart, @@ -46,31 +40,13 @@ import type { RequestHandler, KibanaResponseFactory, RouteMethod, - HttpServicePreboot, HttpServiceSetup, - HttpServiceStart, } from '@kbn/core-http-server'; -import type { PrebootServicePreboot } from '@kbn/core-preboot-server'; -import type { MetricsServiceSetup, MetricsServiceStart } from '@kbn/core-metrics-server'; -import { - ElasticsearchServiceSetup, - ElasticsearchServiceStart, - ElasticsearchServicePreboot, -} from '@kbn/core-elasticsearch-server'; import { configSchema as elasticsearchConfigSchema } from '@kbn/core-elasticsearch-server-internal'; import type { CapabilitiesSetup, CapabilitiesStart } from '@kbn/core-capabilities-server'; -import type { - SavedObjectsServiceSetup, - SavedObjectsServiceStart, -} from '@kbn/core-saved-objects-server'; -import type { DeprecationsServiceSetup } from '@kbn/core-deprecations-server'; -import type { CoreUsageDataStart, CoreUsageDataSetup } from '@kbn/core-usage-data-server'; -import type { I18nServiceSetup } from '@kbn/core-i18n-server'; -import type { StatusServiceSetup } from '@kbn/core-status-server'; -import type { UiSettingsServiceSetup, UiSettingsServiceStart } from '@kbn/core-ui-settings-server'; import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; import type { HttpResources } from '@kbn/core-http-resources-server'; -import { PluginsServiceSetup, PluginsServiceStart } from './plugins'; +import type { PluginsServiceSetup, PluginsServiceStart } from '@kbn/core-plugins-server-internal'; export type { PluginOpaqueId } from '@kbn/core-base-common'; export type { @@ -254,7 +230,6 @@ export type { NodeInfo, NodeRoles } from '@kbn/core-node-server'; export { PluginType } from '@kbn/core-base-common'; export type { - DiscoveredPlugin, PrebootPlugin, Plugin, AsyncPlugin, @@ -263,11 +238,12 @@ export type { PluginInitializer, PluginInitializerContext, PluginManifest, - PluginName, SharedGlobalConfig, MakeUsageFromSchema, ExposedToBrowserDescriptor, -} from './plugins'; +} from '@kbn/core-plugins-server'; + +export type { PluginName, DiscoveredPlugin } from '@kbn/core-base-common'; export type { SavedObject, @@ -467,106 +443,12 @@ export type { PrebootCoreRequestHandlerContext, } from '@kbn/core-http-request-handler-context-server'; -/** - * Context passed to the `setup` method of `preboot` plugins. - * @public - */ -export interface CorePreboot { - /** {@link AnalyticsServicePreboot} */ - analytics: AnalyticsServicePreboot; - /** {@link ElasticsearchServicePreboot} */ - elasticsearch: ElasticsearchServicePreboot; - /** {@link HttpServicePreboot} */ - http: HttpServicePreboot; - /** {@link PrebootServicePreboot} */ - preboot: PrebootServicePreboot; -} - -/** - * Context passed to the `setup` method of `standard` plugins. - * - * @typeParam TPluginsStart - the type of the consuming plugin's start dependencies. Should be the same - * as the consuming {@link Plugin}'s `TPluginsStart` type. Used by `getStartServices`. - * @typeParam TStart - the type of the consuming plugin's start contract. Should be the same as the - * consuming {@link Plugin}'s `TStart` type. Used by `getStartServices`. - * @public - */ -export interface CoreSetup { - /** {@link AnalyticsServiceSetup} */ - analytics: AnalyticsServiceSetup; - /** {@link CapabilitiesSetup} */ - capabilities: CapabilitiesSetup; - /** {@link DocLinksServiceSetup} */ - docLinks: DocLinksServiceSetup; - /** {@link ElasticsearchServiceSetup} */ - elasticsearch: ElasticsearchServiceSetup; - /** {@link ExecutionContextSetup} */ - executionContext: ExecutionContextSetup; - /** {@link HttpServiceSetup} */ - http: HttpServiceSetup & { - /** {@link HttpResources} */ - resources: HttpResources; - }; - /** {@link I18nServiceSetup} */ - i18n: I18nServiceSetup; - /** {@link LoggingServiceSetup} */ - logging: LoggingServiceSetup; - /** {@link MetricsServiceSetup} */ - metrics: MetricsServiceSetup; - /** {@link SavedObjectsServiceSetup} */ - savedObjects: SavedObjectsServiceSetup; - /** {@link StatusServiceSetup} */ - status: StatusServiceSetup; - /** {@link UiSettingsServiceSetup} */ - uiSettings: UiSettingsServiceSetup; - /** {@link DeprecationsServiceSetup} */ - deprecations: DeprecationsServiceSetup; - /** {@link StartServicesAccessor} */ - getStartServices: StartServicesAccessor; - /** @internal {@link CoreUsageDataSetup} */ - coreUsageData: CoreUsageDataSetup; -} - -/** - * Allows plugins to get access to APIs available in start inside async handlers. - * Promise will not resolve until Core and plugin dependencies have completed `start`. - * This should only be used inside handlers registered during `setup` that will only be executed - * after `start` lifecycle. - * - * @public - */ -export type StartServicesAccessor< - TPluginsStart extends object = object, - TStart = unknown -> = () => Promise<[CoreStart, TPluginsStart, TStart]>; - -/** - * Context passed to the plugins `start` method. - * - * @public - */ -export interface CoreStart { - /** {@link AnalyticsServiceStart} */ - analytics: AnalyticsServiceStart; - /** {@link CapabilitiesStart} */ - capabilities: CapabilitiesStart; - /** {@link DocLinksServiceStart} */ - docLinks: DocLinksServiceStart; - /** {@link ElasticsearchServiceStart} */ - elasticsearch: ElasticsearchServiceStart; - /** {@link ExecutionContextStart} */ - executionContext: ExecutionContextStart; - /** {@link HttpServiceStart} */ - http: HttpServiceStart; - /** {@link MetricsServiceStart} */ - metrics: MetricsServiceStart; - /** {@link SavedObjectsServiceStart} */ - savedObjects: SavedObjectsServiceStart; - /** {@link UiSettingsServiceStart} */ - uiSettings: UiSettingsServiceStart; - /** @internal {@link CoreUsageDataStart} */ - coreUsageData: CoreUsageDataStart; -} +export type { + CorePreboot, + CoreSetup, + CoreStart, + StartServicesAccessor, +} from '@kbn/core-lifecycle-server'; export type { CapabilitiesSetup, diff --git a/src/core/server/integration_tests/http/cookie_session_storage.test.ts b/src/core/server/integration_tests/http/cookie_session_storage.test.ts index 713ed2dc9edfd..1041ed66872dd 100644 --- a/src/core/server/integration_tests/http/cookie_session_storage.test.ts +++ b/src/core/server/integration_tests/http/cookie_session_storage.test.ts @@ -53,7 +53,7 @@ configService.atPath.mockImplementation((path) => { ssl: { verificationMode: 'none', }, - compression: { enabled: true }, + compression: { enabled: true, brotli: { enabled: false } }, xsrf: { disableProtection: true, allowlist: [], diff --git a/src/core/server/integration_tests/http/http_server.test.ts b/src/core/server/integration_tests/http/http_server.test.ts index 1b9da1f0fddde..313421fa05eca 100644 --- a/src/core/server/integration_tests/http/http_server.test.ts +++ b/src/core/server/integration_tests/http/http_server.test.ts @@ -31,7 +31,7 @@ describe('Http server', () => { maxPayload: new ByteSizeValue(1024), port: 10002, ssl: { enabled: false }, - compression: { enabled: true }, + compression: { enabled: true, brotli: { enabled: false } }, requestId: { allowFromAnyIp: true, ipAllowlist: [], diff --git a/src/core/server/integration_tests/http/lifecycle_handlers.test.ts b/src/core/server/integration_tests/http/lifecycle_handlers.test.ts index 6e72afd4f4e58..26c17a17b41bb 100644 --- a/src/core/server/integration_tests/http/lifecycle_handlers.test.ts +++ b/src/core/server/integration_tests/http/lifecycle_handlers.test.ts @@ -52,7 +52,7 @@ describe('core lifecycle handlers', () => { cors: { enabled: false, }, - compression: { enabled: true }, + compression: { enabled: true, brotli: { enabled: false } }, name: kibanaName, securityResponseHeaders: { // reflects default config diff --git a/src/core/server/integration_tests/logging/logging.test.ts b/src/core/server/integration_tests/logging/logging.test.ts index a56449550b3cb..09323239a0ce3 100644 --- a/src/core/server/integration_tests/logging/logging.test.ts +++ b/src/core/server/integration_tests/logging/logging.test.ts @@ -8,7 +8,7 @@ import type { LoggerContextConfigInput } from '@kbn/core-logging-server'; import * as kbnTestServer from '../../../test_helpers/kbn_server'; -import { InternalCoreSetup } from '../../internal_types'; +import { InternalCoreSetup } from '@kbn/core-lifecycle-server-internal'; import { Subject } from 'rxjs'; function createRoot() { diff --git a/src/core/server/integration_tests/plugins/jest.integration.config.js b/src/core/server/integration_tests/plugins/jest.integration.config.js deleted file mode 100644 index 55bbf66147bb8..0000000000000 --- a/src/core/server/integration_tests/plugins/jest.integration.config.js +++ /dev/null @@ -1,19 +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 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 or the Server - * Side Public License, v 1. - */ - -module.exports = { - // TODO replace the line below with - // preset: '@kbn/test/jest_integration_node - // to do so, we must fix all integration tests first - // see https://github.com/elastic/kibana/pull/130255/ - preset: '@kbn/test/jest_integration', - rootDir: '../../../../..', - roots: ['/src/core/server/integration_tests/plugins'], - // must override to match all test given there is no `integration_tests` subfolder - testMatch: ['**/*.test.{js,mjs,ts,tsx}'], -}; diff --git a/src/core/server/integration_tests/plugins/plugins_service.test.ts b/src/core/server/integration_tests/plugins/plugins_service.test.ts deleted file mode 100644 index 2d51b63921bad..0000000000000 --- a/src/core/server/integration_tests/plugins/plugins_service.test.ts +++ /dev/null @@ -1,179 +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 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 or the Server - * Side Public License, v 1. - */ - -// must be before mocks imports to avoid conflicting with `REPO_ROOT` accessor. -import { REPO_ROOT } from '@kbn/utils'; -import { mockPackage, mockDiscover } from './plugins_service.test.mocks'; - -import { join } from 'path'; - -import { ConfigPath, ConfigService, Env } from '@kbn/config'; -import { getEnvOptions, rawConfigServiceMock } from '@kbn/config-mocks'; -import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; -import { environmentServiceMock } from '@kbn/core-environment-server-mocks'; -import { nodeServiceMock } from '@kbn/core-node-server-mocks'; -import { PluginsService } from '../../plugins/plugins_service'; -import { BehaviorSubject, from } from 'rxjs'; -import { config } from '../../plugins/plugins_config'; -import { coreMock } from '../../mocks'; -import { AsyncPlugin, PluginType } from '../../plugins/types'; -import { PluginWrapper } from '../../plugins/plugin'; - -describe('PluginsService', () => { - const logger = loggingSystemMock.create(); - const environmentPreboot = environmentServiceMock.createPrebootContract(); - const nodePreboot = nodeServiceMock.createInternalPrebootContract(); - let pluginsService: PluginsService; - - const createPlugin = ( - id: string, - { - path = id, - disabled = false, - version = 'some-version', - requiredPlugins = [], - requiredBundles = [], - optionalPlugins = [], - kibanaVersion = '7.0.0', - type = PluginType.standard, - configPath = [path], - server = true, - ui = true, - owner = { name: 'foo' }, - }: { - path?: string; - disabled?: boolean; - version?: string; - requiredPlugins?: string[]; - requiredBundles?: string[]; - optionalPlugins?: string[]; - kibanaVersion?: string; - type?: PluginType; - configPath?: ConfigPath; - server?: boolean; - ui?: boolean; - owner?: { name: string }; - } - ): PluginWrapper => { - return new PluginWrapper({ - path, - manifest: { - id, - version, - configPath: `${configPath}${disabled ? '-disabled' : ''}`, - kibanaVersion, - type, - requiredPlugins, - requiredBundles, - optionalPlugins, - server, - ui, - owner, - }, - opaqueId: Symbol(id), - initializerContext: { logger } as any, - }); - }; - - beforeEach(async () => { - mockPackage.raw = { - branch: 'feature-v1', - version: 'v1', - build: { - distributable: true, - number: 100, - sha: 'feature-v1-build-sha', - }, - }; - - const env = Env.createDefault(REPO_ROOT, getEnvOptions()); - const config$ = new BehaviorSubject>({ - plugins: { - initialize: true, - }, - }); - const rawConfigService = rawConfigServiceMock.create({ rawConfig$: config$ }); - const configService = new ConfigService(rawConfigService, env, logger); - await configService.setSchema(config.path, config.schema); - - pluginsService = new PluginsService({ - coreId: Symbol('core'), - env, - logger, - configService, - }); - }); - - it("properly resolves `getStartServices` in plugin's lifecycle", async () => { - expect.assertions(6); - - const pluginPath = 'plugin-path'; - - mockDiscover.mockReturnValue({ - error$: from([]), - plugin$: from([ - createPlugin('plugin-id', { - path: pluginPath, - configPath: 'path', - }), - ]), - }); - - let startDependenciesResolved = false; - let contextFromStart: any = null; - let contextFromStartService: any = null; - - const pluginStartContract = { - someApi: () => 'foo', - }; - - const pluginInitializer = () => - ({ - setup: async (coreSetup, deps) => { - coreSetup.getStartServices().then(([core, plugins, pluginStart]) => { - startDependenciesResolved = true; - contextFromStartService = { core, plugins, pluginStart }; - }); - }, - start: async (core, plugins) => { - contextFromStart = { core, plugins }; - await new Promise((resolve) => setTimeout(resolve, 10)); - expect(startDependenciesResolved).toBe(false); - return pluginStartContract; - }, - } as AsyncPlugin); - - jest.doMock( - join(pluginPath, 'server'), - () => ({ - plugin: pluginInitializer, - }), - { - virtual: true, - } - ); - - await pluginsService.discover({ environment: environmentPreboot, node: nodePreboot }); - - const prebootDeps = coreMock.createInternalPreboot(); - await pluginsService.preboot(prebootDeps); - - const setupDeps = coreMock.createInternalSetup(); - await pluginsService.setup(setupDeps); - - expect(startDependenciesResolved).toBe(false); - - const startDeps = coreMock.createInternalStart(); - await pluginsService.start(startDeps); - - expect(startDependenciesResolved).toBe(true); - expect(contextFromStart!.core).toEqual(contextFromStartService!.core); - expect(contextFromStart!.plugins).toEqual(contextFromStartService!.plugins); - expect(contextFromStartService!.pluginStart).toEqual(pluginStartContract); - }); -}); diff --git a/src/core/server/integration_tests/saved_objects/migrations/7.7.2_xpack_100k.test.ts b/src/core/server/integration_tests/saved_objects/migrations/7.7.2_xpack_100k.test.ts index 530a4b92ead6a..225faf5e2c275 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/7.7.2_xpack_100k.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/7.7.2_xpack_100k.test.ts @@ -13,7 +13,7 @@ import { Env } from '@kbn/config'; import { getEnvOptions } from '@kbn/config-mocks'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import * as kbnTestServer from '../../../../test_helpers/kbn_server'; -import { InternalCoreStart } from '../../../internal_types'; +import type { InternalCoreStart } from '@kbn/core-lifecycle-server-internal'; import { Root } from '../../../root'; const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; diff --git a/src/core/server/integration_tests/saved_objects/migrations/actions/es_errors.test.ts b/src/core/server/integration_tests/saved_objects/migrations/actions/es_errors.test.ts index 749be45b35cb7..164fdb19819db 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/actions/es_errors.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/actions/es_errors.test.ts @@ -7,7 +7,7 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ElasticsearchClient } from '../../../..'; -import { InternalCoreStart } from '../../../../internal_types'; +import { InternalCoreStart } from '@kbn/core-lifecycle-server-internal'; import * as kbnTestServer from '../../../../../test_helpers/kbn_server'; import { Root } from '../../../../root'; import { diff --git a/src/core/server/integration_tests/saved_objects/migrations/migration_from_older_v1.test.ts b/src/core/server/integration_tests/saved_objects/migrations/migration_from_older_v1.test.ts index b18d20deaa0e6..3663297058b0c 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/migration_from_older_v1.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/migration_from_older_v1.test.ts @@ -16,7 +16,7 @@ import { getEnvOptions } from '@kbn/config-mocks'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import * as kbnTestServer from '../../../../test_helpers/kbn_server'; -import { InternalCoreStart } from '../../../internal_types'; +import { InternalCoreStart } from '@kbn/core-lifecycle-server-internal'; import { Root } from '../../../root'; const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; diff --git a/src/core/server/integration_tests/saved_objects/migrations/migration_from_same_v1.test.ts b/src/core/server/integration_tests/saved_objects/migrations/migration_from_same_v1.test.ts index f7c0cced08f0f..97369305591a6 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/migration_from_same_v1.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/migration_from_same_v1.test.ts @@ -16,7 +16,7 @@ import { getEnvOptions } from '@kbn/config-mocks'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import * as kbnTestServer from '../../../../test_helpers/kbn_server'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { InternalCoreStart } from '../../../internal_types'; +import { InternalCoreStart } from '@kbn/core-lifecycle-server-internal'; import { Root } from '../../../root'; const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; diff --git a/src/core/server/integration_tests/saved_objects/service/lib/repository.test.ts b/src/core/server/integration_tests/saved_objects/service/lib/repository.test.ts index a8d77abd65baf..d8107b5162f2e 100644 --- a/src/core/server/integration_tests/saved_objects/service/lib/repository.test.ts +++ b/src/core/server/integration_tests/saved_objects/service/lib/repository.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { InternalCoreStart } from '../../../../internal_types'; +import { InternalCoreStart } from '@kbn/core-lifecycle-server-internal'; import * as kbnTestServer from '../../../../../test_helpers/kbn_server'; import { Root } from '../../../../root'; diff --git a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts index f0fdc609d8915..6325d80e9588f 100644 --- a/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts +++ b/src/core/server/integration_tests/saved_objects/service/lib/repository_with_proxy.test.ts @@ -11,7 +11,7 @@ import h2o2 from '@hapi/h2o2'; import { URL } from 'url'; import type { SavedObject } from '@kbn/core-saved-objects-common'; import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; -import { InternalCoreSetup, InternalCoreStart } from '../../../../internal_types'; +import type { InternalCoreSetup, InternalCoreStart } from '@kbn/core-lifecycle-server-internal'; import { Root } from '../../../../root'; import * as kbnTestServer from '../../../../../test_helpers/kbn_server'; import { diff --git a/src/core/server/integration_tests/saved_objects/validation/validator.test.ts b/src/core/server/integration_tests/saved_objects/validation/validator.test.ts index 008bebdc5731f..1157bd6c7499b 100644 --- a/src/core/server/integration_tests/saved_objects/validation/validator.test.ts +++ b/src/core/server/integration_tests/saved_objects/validation/validator.test.ts @@ -15,7 +15,7 @@ import { REPO_ROOT } from '@kbn/utils'; import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; import { getEnvOptions } from '@kbn/config-mocks'; -import { InternalCoreSetup, InternalCoreStart } from '../../../internal_types'; +import type { InternalCoreSetup, InternalCoreStart } from '@kbn/core-lifecycle-server-internal'; import { Root } from '../../../root'; import * as kbnTestServer from '../../../../test_helpers/kbn_server'; diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts deleted file mode 100644 index c66fdf9a968d2..0000000000000 --- a/src/core/server/internal_types.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 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 or the Server - * Side Public License, v 1. - */ - -import type { DocLinksServiceStart, DocLinksServiceSetup } from '@kbn/core-doc-links-server'; -import { - InternalLoggingServicePreboot, - InternalLoggingServiceSetup, -} from '@kbn/core-logging-server-internal'; -import type { - AnalyticsServicePreboot, - AnalyticsServiceSetup, - AnalyticsServiceStart, -} from '@kbn/core-analytics-server'; -import type { InternalEnvironmentServiceSetup } from '@kbn/core-environment-server-internal'; -import type { - InternalExecutionContextSetup, - InternalExecutionContextStart, -} from '@kbn/core-execution-context-server-internal'; -import type { InternalPrebootServicePreboot } from '@kbn/core-preboot-server-internal'; -import type { - InternalContextPreboot, - InternalContextSetup, -} from '@kbn/core-http-context-server-internal'; -import type { - InternalHttpServicePreboot, - InternalHttpServiceSetup, - InternalHttpServiceStart, -} from '@kbn/core-http-server-internal'; -import type { - InternalMetricsServiceSetup, - InternalMetricsServiceStart, -} from '@kbn/core-metrics-server-internal'; -import { - InternalElasticsearchServicePreboot, - InternalElasticsearchServiceSetup, - InternalElasticsearchServiceStart, -} from '@kbn/core-elasticsearch-server-internal'; -import type { CapabilitiesSetup, CapabilitiesStart } from '@kbn/core-capabilities-server'; -import { - InternalSavedObjectsServiceSetup, - InternalSavedObjectsServiceStart, -} from '@kbn/core-saved-objects-server-internal'; -import { - InternalDeprecationsServiceSetup, - InternalDeprecationsServiceStart, -} from '@kbn/core-deprecations-server-internal'; -import type { CoreUsageDataStart } from '@kbn/core-usage-data-server'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { I18nServiceSetup } from '@kbn/core-i18n-server'; -import type { InternalStatusServiceSetup } from '@kbn/core-status-server-internal'; -import type { - InternalUiSettingsServicePreboot, - InternalUiSettingsServiceSetup, - InternalUiSettingsServiceStart, -} from '@kbn/core-ui-settings-server-internal'; -import type { InternalRenderingServiceSetup } from '@kbn/core-rendering-server-internal'; -import type { - InternalHttpResourcesPreboot, - InternalHttpResourcesSetup, -} from '@kbn/core-http-resources-server-internal'; - -/** @internal */ -export interface InternalCorePreboot { - analytics: AnalyticsServicePreboot; - context: InternalContextPreboot; - http: InternalHttpServicePreboot; - elasticsearch: InternalElasticsearchServicePreboot; - uiSettings: InternalUiSettingsServicePreboot; - httpResources: InternalHttpResourcesPreboot; - logging: InternalLoggingServicePreboot; - preboot: InternalPrebootServicePreboot; -} - -/** @internal */ -export interface InternalCoreSetup { - analytics: AnalyticsServiceSetup; - capabilities: CapabilitiesSetup; - context: InternalContextSetup; - docLinks: DocLinksServiceSetup; - http: InternalHttpServiceSetup; - elasticsearch: InternalElasticsearchServiceSetup; - executionContext: InternalExecutionContextSetup; - i18n: I18nServiceSetup; - savedObjects: InternalSavedObjectsServiceSetup; - status: InternalStatusServiceSetup; - uiSettings: InternalUiSettingsServiceSetup; - environment: InternalEnvironmentServiceSetup; - rendering: InternalRenderingServiceSetup; - httpResources: InternalHttpResourcesSetup; - logging: InternalLoggingServiceSetup; - metrics: InternalMetricsServiceSetup; - deprecations: InternalDeprecationsServiceSetup; - coreUsageData: InternalCoreUsageDataSetup; -} - -/** - * @internal - */ -export interface InternalCoreStart { - analytics: AnalyticsServiceStart; - capabilities: CapabilitiesStart; - elasticsearch: InternalElasticsearchServiceStart; - docLinks: DocLinksServiceStart; - http: InternalHttpServiceStart; - metrics: InternalMetricsServiceStart; - savedObjects: InternalSavedObjectsServiceStart; - uiSettings: InternalUiSettingsServiceStart; - coreUsageData: CoreUsageDataStart; - executionContext: InternalExecutionContextStart; - deprecations: InternalDeprecationsServiceStart; -} diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 356fd4deb44d6..028465ebfb8ac 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -10,38 +10,16 @@ import { of } from 'rxjs'; import { duration } from 'moment'; import { ByteSizeValue } from '@kbn/config-schema'; import { isPromise } from '@kbn/std'; -import type { MockedKeys } from '@kbn/utility-types-jest'; -import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; -import { loggingSystemMock, loggingServiceMock } from '@kbn/core-logging-server-mocks'; -import { analyticsServiceMock } from '@kbn/core-analytics-server-mocks'; -import { environmentServiceMock } from '@kbn/core-environment-server-mocks'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { nodeServiceMock } from '@kbn/core-node-server-mocks'; -import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks'; -import { prebootServiceMock } from '@kbn/core-preboot-server-mocks'; -import { contextServiceMock } from '@kbn/core-http-context-server-mocks'; -import { httpServiceMock } from '@kbn/core-http-server-mocks'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { metricsServiceMock } from '@kbn/core-metrics-server-mocks'; -import { capabilitiesServiceMock } from '@kbn/core-capabilities-server-mocks'; import { typeRegistryMock as savedObjectsTypeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { deprecationsServiceMock } from '@kbn/core-deprecations-server-mocks'; -import { coreUsageDataServiceMock } from '@kbn/core-usage-data-server-mocks'; -import { i18nServiceMock } from '@kbn/core-i18n-server-mocks'; -import { statusServiceMock } from '@kbn/core-status-server-mocks'; import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; -import { renderingServiceMock } from '@kbn/core-rendering-server-mocks'; -import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; -import type { - PluginInitializerContext, - CoreSetup, - CoreStart, - StartServicesAccessor, - CorePreboot, - RequestHandlerContext, -} from '.'; -import { SharedGlobalConfig } from './plugins'; +import { coreLifecycleMock, coreInternalLifecycleMock } from '@kbn/core-lifecycle-server-mocks'; +import type { SharedGlobalConfig, PluginInitializerContext } from '@kbn/core-plugins-server'; export { configServiceMock, configDeprecationsMock } from '@kbn/config-mocks'; export { loggingSystemMock } from '@kbn/core-logging-server-mocks'; @@ -132,139 +110,6 @@ function pluginInitializerContextMock(config: T = {} as T) { return mock; } -type CorePrebootMockType = MockedKeys & { - elasticsearch: ReturnType; -}; - -function createCorePrebootMock() { - const mock: CorePrebootMockType = { - analytics: analyticsServiceMock.createAnalyticsServicePreboot(), - elasticsearch: elasticsearchServiceMock.createPreboot(), - http: httpServiceMock.createPrebootContract() as CorePrebootMockType['http'], - preboot: prebootServiceMock.createPrebootContract(), - }; - - return mock; -} - -type CoreSetupMockType = MockedKeys & { - elasticsearch: ReturnType; - getStartServices: jest.MockedFunction>; -}; - -function createCoreSetupMock({ - pluginStartDeps = {}, - pluginStartContract, -}: { - pluginStartDeps?: object; - pluginStartContract?: any; -} = {}) { - const httpMock: jest.Mocked = { - ...httpServiceMock.createSetupContract(), - resources: httpResourcesMock.createRegistrar(), - }; - - const uiSettingsMock = { - register: uiSettingsServiceMock.createSetupContract().register, - }; - - const mock: CoreSetupMockType = { - analytics: analyticsServiceMock.createAnalyticsServiceSetup(), - capabilities: capabilitiesServiceMock.createSetupContract(), - docLinks: docLinksServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createSetup(), - http: httpMock, - i18n: i18nServiceMock.createSetupContract(), - savedObjects: savedObjectsServiceMock.createInternalSetupContract(), - status: statusServiceMock.createSetupContract(), - uiSettings: uiSettingsMock, - logging: loggingServiceMock.createSetupContract(), - metrics: metricsServiceMock.createSetupContract(), - deprecations: deprecationsServiceMock.createSetupContract(), - executionContext: executionContextServiceMock.createInternalSetupContract(), - coreUsageData: { - registerUsageCounter: coreUsageDataServiceMock.createSetupContract().registerUsageCounter, - }, - getStartServices: jest - .fn, object, any]>, []>() - .mockResolvedValue([createCoreStartMock(), pluginStartDeps, pluginStartContract]), - }; - - return mock; -} - -function createCoreStartMock() { - const mock: MockedKeys = { - analytics: analyticsServiceMock.createAnalyticsServiceStart(), - capabilities: capabilitiesServiceMock.createStartContract(), - docLinks: docLinksServiceMock.createStartContract(), - elasticsearch: elasticsearchServiceMock.createStart(), - http: httpServiceMock.createStartContract(), - metrics: metricsServiceMock.createStartContract(), - savedObjects: savedObjectsServiceMock.createStartContract(), - uiSettings: uiSettingsServiceMock.createStartContract(), - coreUsageData: coreUsageDataServiceMock.createStartContract(), - executionContext: executionContextServiceMock.createInternalStartContract(), - }; - - return mock; -} - -function createInternalCorePrebootMock() { - const prebootDeps = { - analytics: analyticsServiceMock.createAnalyticsServicePreboot(), - context: contextServiceMock.createPrebootContract(), - elasticsearch: elasticsearchServiceMock.createInternalPreboot(), - http: httpServiceMock.createInternalPrebootContract(), - httpResources: httpResourcesMock.createPrebootContract(), - uiSettings: uiSettingsServiceMock.createPrebootContract(), - logging: loggingServiceMock.createInternalPrebootContract(), - preboot: prebootServiceMock.createInternalPrebootContract(), - }; - return prebootDeps; -} - -function createInternalCoreSetupMock() { - const setupDeps = { - analytics: analyticsServiceMock.createAnalyticsServiceSetup(), - capabilities: capabilitiesServiceMock.createSetupContract(), - context: contextServiceMock.createSetupContract(), - docLinks: docLinksServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createInternalSetup(), - http: httpServiceMock.createInternalSetupContract(), - savedObjects: savedObjectsServiceMock.createInternalSetupContract(), - status: statusServiceMock.createInternalSetupContract(), - environment: environmentServiceMock.createSetupContract(), - i18n: i18nServiceMock.createSetupContract(), - httpResources: httpResourcesMock.createSetupContract(), - rendering: renderingServiceMock.createSetupContract(), - uiSettings: uiSettingsServiceMock.createSetupContract(), - logging: loggingServiceMock.createInternalSetupContract(), - metrics: metricsServiceMock.createInternalSetupContract(), - deprecations: deprecationsServiceMock.createInternalSetupContract(), - executionContext: executionContextServiceMock.createInternalSetupContract(), - coreUsageData: coreUsageDataServiceMock.createSetupContract(), - }; - return setupDeps; -} - -function createInternalCoreStartMock() { - const startDeps = { - analytics: analyticsServiceMock.createAnalyticsServiceStart(), - capabilities: capabilitiesServiceMock.createStartContract(), - docLinks: docLinksServiceMock.createStartContract(), - elasticsearch: elasticsearchServiceMock.createInternalStart(), - http: httpServiceMock.createInternalStartContract(), - metrics: metricsServiceMock.createInternalStartContract(), - savedObjects: savedObjectsServiceMock.createInternalStartContract(), - uiSettings: uiSettingsServiceMock.createStartContract(), - coreUsageData: coreUsageDataServiceMock.createStartContract(), - executionContext: executionContextServiceMock.createInternalStartContract(), - deprecations: deprecationsServiceMock.createInternalStartContract(), - }; - return startDeps; -} - function createCoreRequestHandlerContextMock() { return { savedObjects: { @@ -321,12 +166,12 @@ const createCustomRequestHandlerContextMock = (contextParts: T): CustomReques }; export const coreMock = { - createPreboot: createCorePrebootMock, - createSetup: createCoreSetupMock, - createStart: createCoreStartMock, - createInternalPreboot: createInternalCorePrebootMock, - createInternalSetup: createInternalCoreSetupMock, - createInternalStart: createInternalCoreStartMock, + createPreboot: coreLifecycleMock.createPreboot, + createSetup: coreLifecycleMock.createCoreSetup, + createStart: coreLifecycleMock.createCoreStart, + createInternalPreboot: coreInternalLifecycleMock.createInternalPreboot, + createInternalSetup: coreInternalLifecycleMock.createInternalSetup, + createInternalStart: coreInternalLifecycleMock.createInternalStart, createPluginInitializerContext: pluginInitializerContextMock, createRequestHandlerContext: createCoreRequestHandlerContextMock, createCustomRequestHandlerContext: createCustomRequestHandlerContextMock, diff --git a/src/core/server/root/elastic_config.ts b/src/core/server/root/elastic_config.ts new file mode 100644 index 0000000000000..84b2ce394962a --- /dev/null +++ b/src/core/server/root/elastic_config.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 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 or the Server + * Side Public License, v 1. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { apmConfigSchema } from '@kbn/apm-config-loader'; +import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; + +const elasticConfig = schema.object({ + apm: apmConfigSchema, +}); + +export type ElasticConfigType = TypeOf; + +export const elasticApmConfig: ServiceConfigDescriptor = { + path: 'elastic', + schema: elasticConfig, +}; diff --git a/src/core/server/root/index.ts b/src/core/server/root/index.ts index 2f3bcce039849..e4a129188ea6b 100644 --- a/src/core/server/root/index.ts +++ b/src/core/server/root/index.ts @@ -7,10 +7,20 @@ */ import { ConnectableObservable, Subscription } from 'rxjs'; -import { first, publishReplay, switchMap, concatMap, tap } from 'rxjs/operators'; +import { + first, + publishReplay, + switchMap, + concatMap, + tap, + distinctUntilChanged, +} from 'rxjs/operators'; import type { Logger, LoggerFactory } from '@kbn/logging'; import { Env, RawConfigurationProvider } from '@kbn/config'; import { LoggingConfigType, LoggingSystem } from '@kbn/core-logging-server-internal'; +import apm from 'elastic-apm-node'; +import { isEqual } from 'lodash'; +import type { ElasticConfigType } from './elastic_config'; import { Server } from '../server'; /** @@ -22,6 +32,7 @@ export class Root { private readonly loggingSystem: LoggingSystem; private readonly server: Server; private loggingConfigSubscription?: Subscription; + private apmConfigSubscription?: Subscription; constructor( rawConfigProvider: RawConfigurationProvider, @@ -37,7 +48,9 @@ export class Root { public async preboot() { try { this.server.setupCoreConfig(); + this.setupApmLabelSync(); await this.setupLogging(); + this.log.debug('prebooting root'); return await this.server.preboot(); } catch (e) { @@ -85,6 +98,10 @@ export class Root { this.loggingConfigSubscription.unsubscribe(); this.loggingConfigSubscription = undefined; } + if (this.apmConfigSubscription !== undefined) { + this.apmConfigSubscription.unsubscribe(); + this.apmConfigSubscription = undefined; + } await this.loggingSystem.stop(); if (this.onShutdown !== undefined) { @@ -92,6 +109,23 @@ export class Root { } } + private setupApmLabelSync() { + const { configService } = this.server; + + // Update APM labels on config change + this.apmConfigSubscription = configService + .getConfig$() + .pipe( + switchMap(() => configService.atPath('elastic')), + distinctUntilChanged(isEqual), + tap((elasticConfig) => { + const labels = elasticConfig.apm?.globalLabels || {}; + apm.addLabels(labels); + }) + ) + .subscribe(); + } + private async setupLogging() { const { configService } = this.server; // Stream that maps config updates to logger updates, including update failures. diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts index 3e1d5c0e3a28f..01f856f6b3a89 100644 --- a/src/core/server/server.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -13,10 +13,10 @@ jest.doMock('@kbn/core-http-server-internal', () => ({ HttpService: jest.fn(() => mockHttpService), })); -import { pluginServiceMock } from './plugins/plugins_service.mock'; +import { pluginServiceMock } from '@kbn/core-plugins-server-mocks'; export const mockPluginsService = pluginServiceMock.create(); -jest.doMock('./plugins/plugins_service', () => ({ +jest.doMock('@kbn/core-plugins-server-internal', () => ({ PluginsService: jest.fn(() => mockPluginsService), })); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 0949b9ee65f8a..1f60c3242215a 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -69,10 +69,18 @@ import type { import { RenderingService } from '@kbn/core-rendering-server-internal'; import { HttpResourcesService } from '@kbn/core-http-resources-server-internal'; +import { + InternalCorePreboot, + InternalCoreSetup, + InternalCoreStart, +} from '@kbn/core-lifecycle-server-internal'; +import { + DiscoveredPlugins, + PluginsService, + config as pluginsConfig, +} from '@kbn/core-plugins-server-internal'; import { CoreApp } from './core_app'; -import { PluginsService, config as pluginsConfig } from './plugins'; -import { InternalCorePreboot, InternalCoreSetup, InternalCoreStart } from './internal_types'; -import { DiscoveredPlugins } from './plugins'; +import { elasticApmConfig } from './root/elastic_config'; const coreId = Symbol('core'); const rootConfigPath = ''; @@ -450,6 +458,7 @@ export class Server { cspConfig, deprecationConfig, elasticsearchConfig, + elasticApmConfig, executionContextConfig, externalUrlConfig, httpConfig, diff --git a/src/core/test_helpers/kbn_server.ts b/src/core/test_helpers/kbn_server.ts index faa45c52d84b8..64c32c47ca2a9 100644 --- a/src/core/test_helpers/kbn_server.ts +++ b/src/core/test_helpers/kbn_server.ts @@ -21,7 +21,7 @@ import { } from '@kbn/test'; import { CliArgs, Env } from '@kbn/config'; -import { InternalCoreSetup, InternalCoreStart } from '../server/internal_types'; +import type { InternalCoreSetup, InternalCoreStart } from '@kbn/core-lifecycle-server-internal'; import { Root } from '../server/root'; export type HttpMethod = 'delete' | 'get' | 'head' | 'post' | 'put' | 'patch'; diff --git a/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts index 22d3fb2a44689..377ae064a439c 100644 --- a/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts +++ b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts @@ -9,7 +9,7 @@ import { TimedItemBuffer } from '../timed_item_buffer'; import { runItemBufferTests } from './run_item_buffer_tests'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); beforeEach(() => { jest.clearAllTimers(); diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts index 14fccf09d83b0..d3aaaf256f2eb 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts @@ -11,7 +11,8 @@ import { fetchStreaming as fetchStreamingReal } from '../streaming/fetch_streami import { AbortError, defer, of } from '@kbn/kibana-utils-plugin/public'; import { Subject } from 'rxjs'; -const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); +const flushPromises = () => + new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); const getPromiseState = (promise: Promise): Promise<'resolved' | 'rejected' | 'pending'> => Promise.race<'resolved' | 'rejected' | 'pending'>([ @@ -50,7 +51,7 @@ const setup = () => { describe('createStreamingBatchedFunction()', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap index 96b70e33021f4..1b644ef0a4938 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap @@ -77,6 +77,7 @@ Object { "xAccessor": "col-1-2", "yAccessor": undefined, }, + "canNavigateToLens": false, "data": Object { "columns": Array [ Object { diff --git a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts index 548d4ec0ab49e..f0c309de19236 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts +++ b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts @@ -232,6 +232,7 @@ export const heatmapFunction = (): HeatmapExpressionFunctionDefinition => ({ }, syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false, syncCursor: handlers?.isSyncCursorEnabled?.() ?? true, + canNavigateToLens: Boolean(handlers?.variables?.canNavigateToLens), }, }; }, diff --git a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts index 5aa1507f30b03..1bf5fe3bbb36b 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts @@ -94,6 +94,7 @@ export interface HeatmapExpressionProps { args: HeatmapArguments; syncTooltips: boolean; syncCursor: boolean; + canNavigateToLens?: boolean; } export interface HeatmapRender { diff --git a/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx b/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx index 4b813fb93416f..b14ee1382deb2 100644 --- a/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx +++ b/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx @@ -61,9 +61,14 @@ export const heatmapRenderer: ( const visualizationType = extractVisualizationType(executionContext); if (containerType && visualizationType) { - plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, [ + const events = [ `render_${visualizationType}_${EXPRESSION_HEATMAP_NAME}`, - ]); + config.canNavigateToLens + ? `render_${visualizationType}_${EXPRESSION_HEATMAP_NAME}_convertable` + : undefined, + ].filter((event): event is string => Boolean(event)); + + plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, events); } handlers.done(); diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index ed8c87b5df147..10636f452004d 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -115,7 +115,9 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) { const loadBufferFromRemote = (url: string) => { const coreEditor = editor.getCoreEditor(); - if (/^https?:\/\//.test(url)) { + // Normalize and encode the URL to avoid issues with spaces and other special characters. + const encodedUrl = new URL(url).toString(); + if (/^https?:\/\//.test(encodedUrl)) { const loadFrom: Record = { url, // Having dataType here is required as it doesn't allow jQuery to `eval` content diff --git a/src/plugins/console/public/lib/autocomplete_entities/mapping.ts b/src/plugins/console/public/lib/autocomplete_entities/mapping.ts index ddb6905fa6e53..71e72dac0a280 100644 --- a/src/plugins/console/public/lib/autocomplete_entities/mapping.ts +++ b/src/plugins/console/public/lib/autocomplete_entities/mapping.ts @@ -121,23 +121,9 @@ export class Mapping implements BaseMapping { }; loadMappings = (mappings: IndicesGetMappingResponse) => { - const maxMappingSize = Object.keys(mappings).length > 10 * 1024 * 1024; - let mappingsResponse; - if (maxMappingSize) { - // eslint-disable-next-line no-console - console.warn( - `Mapping size is larger than 10MB (${ - Object.keys(mappings).length / 1024 / 1024 - } MB). Ignoring...` - ); - mappingsResponse = {}; - } else { - mappingsResponse = mappings; - } - this.perIndexTypes = {}; - Object.entries(mappingsResponse).forEach(([index, indexMapping]) => { + Object.entries(mappings).forEach(([index, indexMapping]) => { const normalizedIndexMappings: Record = {}; let transformedMapping: Record = indexMapping; diff --git a/src/plugins/console/server/lib/utils/index.ts b/src/plugins/console/server/lib/utils/index.ts index 0da3e36b575e3..2c595640eefbf 100644 --- a/src/plugins/console/server/lib/utils/index.ts +++ b/src/plugins/console/server/lib/utils/index.ts @@ -8,3 +8,4 @@ export { encodePath } from './encode_path'; export { toURL } from './to_url'; +export { streamToJSON } from './stream_to_json'; diff --git a/src/plugins/console/server/lib/utils/stream_to_json.test.ts b/src/plugins/console/server/lib/utils/stream_to_json.test.ts new file mode 100644 index 0000000000000..780d3adb4a145 --- /dev/null +++ b/src/plugins/console/server/lib/utils/stream_to_json.test.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 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 or the Server + * Side Public License, v 1. + */ + +import { Readable } from 'stream'; +import { streamToJSON } from './stream_to_json'; +import type { IncomingMessage } from 'http'; + +describe('streamToString', () => { + it('should limit the response size', async () => { + const stream = new Readable({ + read() { + this.push('a'.repeat(1000)); + }, + }); + await expect( + streamToJSON(stream as IncomingMessage, 500) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Response size limit exceeded"`); + }); + + it('should parse the response', async () => { + const stream = new Readable({ + read() { + this.push('{"test": "test"}'); + this.push(null); + }, + }); + const result = await streamToJSON(stream as IncomingMessage, 5000); + expect(result).toEqual({ test: 'test' }); + }); +}); diff --git a/src/plugins/console/server/lib/utils/stream_to_json.ts b/src/plugins/console/server/lib/utils/stream_to_json.ts new file mode 100644 index 0000000000000..5ff1974e9fd47 --- /dev/null +++ b/src/plugins/console/server/lib/utils/stream_to_json.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 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 or the Server + * Side Public License, v 1. + */ + +import type { IncomingMessage } from 'http'; + +export function streamToJSON(stream: IncomingMessage, limit: number) { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + stream.on('data', (chunk) => { + chunks.push(chunk); + if (Buffer.byteLength(Buffer.concat(chunks)) > limit) { + stream.destroy(); + reject(new Error('Response size limit exceeded')); + } + }); + stream.on('end', () => { + const response = Buffer.concat(chunks).toString('utf8'); + resolve(JSON.parse(response)); + }); + stream.on('error', reject); + }); +} diff --git a/src/plugins/console/server/routes/api/console/autocomplete_entities/register_get_route.ts b/src/plugins/console/server/routes/api/console/autocomplete_entities/register_get_route.ts index 9d5778f0a9b0f..98bd51e901b09 100644 --- a/src/plugins/console/server/routes/api/console/autocomplete_entities/register_get_route.ts +++ b/src/plugins/console/server/routes/api/console/autocomplete_entities/register_get_route.ts @@ -7,8 +7,10 @@ */ import type { IScopedClusterClient } from '@kbn/core/server'; import { parse } from 'query-string'; +import type { IncomingMessage } from 'http'; import type { RouteDependencies } from '../../..'; import { API_BASE_PATH } from '../../../../../common/constants'; +import { streamToJSON } from '../../../../lib/utils'; interface Settings { indices: boolean; @@ -17,40 +19,74 @@ interface Settings { dataStreams: boolean; } +const RESPONSE_SIZE_LIMIT = 10 * 1024 * 1024; +// Limit the response size to 10MB, because the response can be very large and sending it to the client +// can cause the browser to hang. + async function getMappings(esClient: IScopedClusterClient, settings: Settings) { if (settings.fields) { - return esClient.asInternalUser.indices.getMapping(); + const stream = await esClient.asInternalUser.indices.getMapping(undefined, { + asStream: true, + }); + return streamToJSON(stream as unknown as IncomingMessage, RESPONSE_SIZE_LIMIT); } // If the user doesn't want autocomplete suggestions, then clear any that exist. - return Promise.resolve({}); + return {}; } async function getAliases(esClient: IScopedClusterClient, settings: Settings) { if (settings.indices) { - return esClient.asInternalUser.indices.getAlias(); + const stream = await esClient.asInternalUser.indices.getAlias(undefined, { + asStream: true, + }); + return streamToJSON(stream as unknown as IncomingMessage, RESPONSE_SIZE_LIMIT); } // If the user doesn't want autocomplete suggestions, then clear any that exist. - return Promise.resolve({}); + return {}; } async function getDataStreams(esClient: IScopedClusterClient, settings: Settings) { if (settings.dataStreams) { - return esClient.asInternalUser.indices.getDataStream(); + const stream = await esClient.asInternalUser.indices.getDataStream(undefined, { + asStream: true, + }); + return streamToJSON(stream as unknown as IncomingMessage, RESPONSE_SIZE_LIMIT); + } + // If the user doesn't want autocomplete suggestions, then clear any that exist. + return {}; +} + +async function getLegacyTemplates(esClient: IScopedClusterClient, settings: Settings) { + if (settings.templates) { + const stream = await esClient.asInternalUser.indices.getTemplate(undefined, { + asStream: true, + }); + return streamToJSON(stream as unknown as IncomingMessage, RESPONSE_SIZE_LIMIT); + } + // If the user doesn't want autocomplete suggestions, then clear any that exist. + return {}; +} + +async function getComponentTemplates(esClient: IScopedClusterClient, settings: Settings) { + if (settings.templates) { + const stream = await esClient.asInternalUser.cluster.getComponentTemplate(undefined, { + asStream: true, + }); + return streamToJSON(stream as unknown as IncomingMessage, RESPONSE_SIZE_LIMIT); } // If the user doesn't want autocomplete suggestions, then clear any that exist. - return Promise.resolve({}); + return {}; } -async function getTemplates(esClient: IScopedClusterClient, settings: Settings) { +async function getIndexTemplates(esClient: IScopedClusterClient, settings: Settings) { if (settings.templates) { - return Promise.all([ - esClient.asInternalUser.indices.getTemplate(), - esClient.asInternalUser.indices.getIndexTemplate(), - esClient.asInternalUser.cluster.getComponentTemplate(), - ]); + const stream = await esClient.asInternalUser.indices.getIndexTemplate(undefined, { + asStream: true, + }); + return streamToJSON(stream as unknown as IncomingMessage, RESPONSE_SIZE_LIMIT); } // If the user doesn't want autocomplete suggestions, then clear any that exist. - return Promise.resolve([]); + return {}; } export function registerGetRoute({ router, lib: { handleEsError } }: RouteDependencies) { @@ -71,11 +107,32 @@ export function registerGetRoute({ router, lib: { handleEsError } }: RouteDepend } const esClient = (await ctx.core).elasticsearch.client; - const mappings = await getMappings(esClient, settings); - const aliases = await getAliases(esClient, settings); - const dataStreams = await getDataStreams(esClient, settings); - const [legacyTemplates = {}, indexTemplates = {}, componentTemplates = {}] = - await getTemplates(esClient, settings); + + // Wait for all requests to complete, in case one of them fails return the successfull ones + const results = await Promise.allSettled([ + getMappings(esClient, settings), + getAliases(esClient, settings), + getDataStreams(esClient, settings), + getLegacyTemplates(esClient, settings), + getIndexTemplates(esClient, settings), + getComponentTemplates(esClient, settings), + ]); + + const [ + mappings, + aliases, + dataStreams, + legacyTemplates, + indexTemplates, + componentTemplates, + ] = results.map((result) => { + // If the request was successful, return the result + if (result.status === 'fulfilled') { + return result.value; + } + // If the request failed, return an empty object + return {}; + }); return response.ok({ body: { diff --git a/src/plugins/controls/common/control_group/control_group_panel_diff_system.ts b/src/plugins/controls/common/control_group/control_group_panel_diff_system.ts new file mode 100644 index 0000000000000..0ef2438494d7e --- /dev/null +++ b/src/plugins/controls/common/control_group/control_group_panel_diff_system.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 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 or the Server + * Side Public License, v 1. + */ + +import deepEqual from 'fast-deep-equal'; +import { omit, isEqual } from 'lodash'; +import { OptionsListEmbeddableInput, OPTIONS_LIST_CONTROL } from '../options_list/types'; + +import { ControlPanelState } from './types'; + +interface DiffSystem { + getPanelIsEqual: (initialInput: ControlPanelState, newInput: ControlPanelState) => boolean; +} + +export const genericControlPanelDiffSystem: DiffSystem = { + getPanelIsEqual: (initialInput, newInput) => { + return deepEqual(initialInput, newInput); + }, +}; + +export const ControlPanelDiffSystems: { + [key: string]: DiffSystem; +} = { + [OPTIONS_LIST_CONTROL]: { + getPanelIsEqual: (initialInput, newInput) => { + if (!deepEqual(omit(initialInput, 'explicitInput'), omit(newInput, 'explicitInput'))) { + return false; + } + + const { + exclude: excludeA, + selectedOptions: selectedA, + singleSelect: singleSelectA, + hideExclude: hideExcludeA, + runPastTimeout: runPastTimeoutA, + ...inputA + }: Partial = initialInput.explicitInput; + const { + exclude: excludeB, + selectedOptions: selectedB, + singleSelect: singleSelectB, + hideExclude: hideExcludeB, + runPastTimeout: runPastTimeoutB, + ...inputB + }: Partial = newInput.explicitInput; + + return ( + Boolean(excludeA) === Boolean(excludeB) && + Boolean(singleSelectA) === Boolean(singleSelectB) && + Boolean(hideExcludeA) === Boolean(hideExcludeB) && + Boolean(runPastTimeoutA) === Boolean(runPastTimeoutB) && + isEqual(selectedA ?? [], selectedB ?? []) && + deepEqual(inputA, inputB) + ); + }, + }, +}; diff --git a/src/plugins/controls/common/control_group/control_group_persistence.ts b/src/plugins/controls/common/control_group/control_group_persistence.ts index 16c06297b6fde..1dbe096307c50 100644 --- a/src/plugins/controls/common/control_group/control_group_persistence.ts +++ b/src/plugins/controls/common/control_group/control_group_persistence.ts @@ -9,7 +9,7 @@ import { SerializableRecord } from '@kbn/utility-types'; import deepEqual from 'fast-deep-equal'; -import { pick } from 'lodash'; +import { pick, omit, xor } from 'lodash'; import { ControlGroupInput } from '..'; import { DEFAULT_CONTROL_GROW, @@ -17,6 +17,10 @@ import { DEFAULT_CONTROL_WIDTH, } from './control_group_constants'; import { PersistableControlGroupInput, RawControlGroupAttributes } from './types'; +import { + ControlPanelDiffSystems, + genericControlPanelDiffSystem, +} from './control_group_panel_diff_system'; const safeJSONParse = (jsonString?: string): OutType | undefined => { if (!jsonString && typeof jsonString !== 'string') return; @@ -54,10 +58,40 @@ export const persistableControlGroupInputIsEqual = ( ...defaultInput, ...pick(b, ['panels', 'chainingSystem', 'controlStyle', 'ignoreParentSettings']), }; - if (deepEqual(inputA, inputB)) return true; + + if ( + getPanelsAreEqual(inputA.panels, inputB.panels) && + deepEqual(omit(inputA, 'panels'), omit(inputB, 'panels')) + ) + return true; + return false; }; +const getPanelsAreEqual = ( + originalPanels: PersistableControlGroupInput['panels'], + newPanels: PersistableControlGroupInput['panels'] +) => { + const originalPanelIds = Object.keys(originalPanels); + const newPanelIds = Object.keys(newPanels); + const panelIdDiff = xor(originalPanelIds, newPanelIds); + if (panelIdDiff.length > 0) { + return false; + } + + for (const panelId of newPanelIds) { + const newPanelType = newPanels[panelId].type; + const panelIsEqual = ControlPanelDiffSystems[newPanelType] + ? ControlPanelDiffSystems[newPanelType].getPanelIsEqual( + originalPanels[panelId], + newPanels[panelId] + ) + : genericControlPanelDiffSystem.getPanelIsEqual(originalPanels[panelId], newPanels[panelId]); + if (!panelIsEqual) return false; + } + return true; +}; + export const controlGroupInputToRawControlGroupAttributes = ( controlGroupInput: Omit ): RawControlGroupAttributes => { diff --git a/src/plugins/controls/common/options_list/mocks.tsx b/src/plugins/controls/common/options_list/mocks.tsx index 09f6d9caa33b4..ccbe3a1132479 100644 --- a/src/plugins/controls/common/options_list/mocks.tsx +++ b/src/plugins/controls/common/options_list/mocks.tsx @@ -28,6 +28,8 @@ const mockOptionsListEmbeddableInput = { selectedOptions: [], runPastTimeout: false, singleSelect: false, + allowExclude: false, + exclude: false, } as OptionsListEmbeddableInput; const mockOptionsListOutput = { diff --git a/src/plugins/controls/common/options_list/types.ts b/src/plugins/controls/common/options_list/types.ts index 25db6c50349c4..59b78ee38d0b9 100644 --- a/src/plugins/controls/common/options_list/types.ts +++ b/src/plugins/controls/common/options_list/types.ts @@ -17,6 +17,8 @@ export interface OptionsListEmbeddableInput extends DataControlInput { selectedOptions?: string[]; runPastTimeout?: boolean; singleSelect?: boolean; + hideExclude?: boolean; + exclude?: boolean; } export type OptionsListField = FieldSpec & { diff --git a/src/plugins/controls/public/control_group/component/control_frame_component.tsx b/src/plugins/controls/public/control_group/component/control_frame_component.tsx index 196b2cfe28be3..8f53e2e29c63e 100644 --- a/src/plugins/controls/public/control_group/component/control_frame_component.tsx +++ b/src/plugins/controls/public/control_group/component/control_frame_component.tsx @@ -13,12 +13,17 @@ import { EuiFormControlLayout, EuiFormLabel, EuiFormRow, + EuiIcon, + EuiLink, EuiLoadingChart, + EuiPopover, + EuiText, EuiToolTip, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { Markdown } from '@kbn/kibana-react-plugin/public'; import { useReduxContainerContext } from '@kbn/presentation-util-plugin/public'; -import { ErrorEmbeddable } from '@kbn/embeddable-plugin/public'; import { ControlGroupReduxState } from '../types'; import { pluginServices } from '../../services'; import { EditControlButton } from '../editor/edit_control'; @@ -26,6 +31,44 @@ import { ControlGroupStrings } from '../control_group_strings'; import { useChildEmbeddable } from '../../hooks/use_child_embeddable'; import { TIME_SLIDER_CONTROL } from '../../../common'; +interface ControlFrameErrorProps { + error: Error; +} + +const ControlFrameError = ({ error }: ControlFrameErrorProps) => { + const [isPopoverOpen, setPopoverOpen] = useState(false); + const popoverButton = ( + + setPopoverOpen((open) => !open)} + > + + + + + ); + + return ( + setPopoverOpen(false)} + > + + + ); +}; + export interface ControlFrameProps { customPrepend?: JSX.Element; enableActions?: boolean; @@ -40,7 +83,7 @@ export const ControlFrame = ({ embeddableType, }: ControlFrameProps) => { const embeddableRoot: React.RefObject = useMemo(() => React.createRef(), []); - const [hasFatalError, setHasFatalError] = useState(false); + const [fatalError, setFatalError] = useState(); const { useEmbeddableSelector: select, @@ -61,19 +104,14 @@ export const ControlFrame = ({ const usingTwoLineLayout = controlStyle === 'twoLine'; useEffect(() => { - if (embeddableRoot.current && embeddable) { - embeddable.render(embeddableRoot.current); + if (embeddableRoot.current) { + embeddable?.render(embeddableRoot.current); } const inputSubscription = embeddable ?.getInput$() .subscribe((newInput) => setTitle(newInput.title)); const errorSubscription = embeddable?.getOutput$().subscribe({ - error: (error: Error) => { - if (!embeddableRoot.current) return; - const errorEmbeddable = new ErrorEmbeddable(error, { id: embeddable.id }, undefined, true); - errorEmbeddable.render(embeddableRoot.current); - setHasFatalError(true); - }, + error: setFatalError, }); return () => { inputSubscription?.unsubscribe(); @@ -88,7 +126,7 @@ export const ControlFrame = ({ 'controlFrameFloatingActions--oneLine': !usingTwoLineLayout, })} > - {!hasFatalError && embeddableType !== TIME_SLIDER_CONTROL && ( + {!fatalError && embeddableType !== TIME_SLIDER_CONTROL && ( @@ -119,7 +157,7 @@ export const ControlFrame = ({ const embeddableParentClassNames = classNames('controlFrame__control', { 'controlFrame--twoLine': controlStyle === 'twoLine', 'controlFrame--oneLine': controlStyle === 'oneLine', - 'controlFrame--fatalError': hasFatalError, + 'controlFrame--fatalError': !!fatalError, }); function renderEmbeddablePrepend() { @@ -149,12 +187,19 @@ export const ControlFrame = ({ } > - {embeddable && ( + {embeddable && !fatalError && (
    + > + {fatalError && } +
    + )} + {fatalError && ( +
    + {} +
    )} {!embeddable && (
    diff --git a/src/plugins/controls/public/options_list/components/options_list_control.tsx b/src/plugins/controls/public/options_list/components/options_list_control.tsx index 7eb8fe783cc0b..93bf0cbef864e 100644 --- a/src/plugins/controls/public/options_list/components/options_list_control.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_control.tsx @@ -11,7 +11,13 @@ import classNames from 'classnames'; import { debounce, isEmpty } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; -import { EuiFilterButton, EuiFilterGroup, EuiPopover, useResizeObserver } from '@elastic/eui'; +import { + EuiFilterButton, + EuiFilterGroup, + EuiPopover, + EuiTextColor, + useResizeObserver, +} from '@elastic/eui'; import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public'; import { OptionsListStrings } from './options_list_strings'; @@ -43,6 +49,7 @@ export const OptionsListControl = ({ typeaheadSubject }: { typeaheadSubject: Sub const controlStyle = select((state) => state.explicitInput.controlStyle); const singleSelect = select((state) => state.explicitInput.singleSelect); const id = select((state) => state.explicitInput.id); + const exclude = select((state) => state.explicitInput.exclude); const loading = select((state) => state.output.loading); @@ -75,6 +82,11 @@ export const OptionsListControl = ({ typeaheadSubject }: { typeaheadSubject: Sub validSelectionsCount: validSelections?.length, selectionDisplayNode: ( <> + {exclude && ( + + {OptionsListStrings.control.getNegate()}{' '} + + )} {validSelections && ( {validSelections?.join(OptionsListStrings.control.getSeparator())} )} @@ -86,7 +98,7 @@ export const OptionsListControl = ({ typeaheadSubject }: { typeaheadSubject: Sub ), }; - }, [validSelections, invalidSelections]); + }, [exclude, validSelections, invalidSelections]); const button = (
    diff --git a/src/plugins/controls/public/options_list/components/options_list_editor_options.tsx b/src/plugins/controls/public/options_list/components/options_list_editor_options.tsx index 0cd96232b714e..0723fcaad6ba6 100644 --- a/src/plugins/controls/public/options_list/components/options_list_editor_options.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_editor_options.tsx @@ -8,7 +8,8 @@ import React, { useState } from 'react'; -import { EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIconTip, EuiSwitch } from '@elastic/eui'; +import { css } from '@emotion/react'; import { OptionsListStrings } from './options_list_strings'; import { ControlEditorProps, OptionsListEmbeddableInput } from '../..'; @@ -16,6 +17,7 @@ import { ControlEditorProps, OptionsListEmbeddableInput } from '../..'; interface OptionsListEditorState { singleSelect?: boolean; runPastTimeout?: boolean; + hideExclude?: boolean; } export const OptionsListEditorOptions = ({ @@ -25,6 +27,7 @@ export const OptionsListEditorOptions = ({ const [state, setState] = useState({ singleSelect: initialInput?.singleSelect, runPastTimeout: initialInput?.runPastTimeout, + hideExclude: initialInput?.hideExclude, }); return ( @@ -41,14 +44,40 @@ export const OptionsListEditorOptions = ({ { - onChange({ runPastTimeout: !state.runPastTimeout }); - setState((s) => ({ ...s, runPastTimeout: !s.runPastTimeout })); + onChange({ hideExclude: !state.hideExclude }); + setState((s) => ({ ...s, hideExclude: !s.hideExclude })); + if (initialInput?.exclude) onChange({ exclude: false }); }} /> + + + + { + onChange({ runPastTimeout: !state.runPastTimeout }); + setState((s) => ({ ...s, runPastTimeout: !s.runPastTimeout })); + }} + /> + + + + + + ); }; diff --git a/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx b/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx index f30d9a785ffec..eca6fe72376a1 100644 --- a/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_popover.test.tsx @@ -97,4 +97,22 @@ describe('Options list popover', () => { expect(child.text()).toBe(selections[i]); }); }); + + test('should default to exclude = false', () => { + const popover = mountComponent(); + const includeButton = findTestSubject(popover, 'optionsList__includeResults'); + const excludeButton = findTestSubject(popover, 'optionsList__excludeResults'); + expect(includeButton.prop('checked')).toBe(true); + expect(excludeButton.prop('checked')).toBeFalsy(); + }); + + test('if exclude = true, select appropriate button in button group', () => { + const popover = mountComponent({ + explicitInput: { exclude: true }, + }); + const includeButton = findTestSubject(popover, 'optionsList__includeResults'); + const excludeButton = findTestSubject(popover, 'optionsList__excludeResults'); + expect(includeButton.prop('checked')).toBeFalsy(); + expect(excludeButton.prop('checked')).toBe(true); + }); }); diff --git a/src/plugins/controls/public/options_list/components/options_list_popover.tsx b/src/plugins/controls/public/options_list/components/options_list_popover.tsx index 8863a3d1978a3..48be0d9253285 100644 --- a/src/plugins/controls/public/options_list/components/options_list_popover.tsx +++ b/src/plugins/controls/public/options_list/components/options_list_popover.tsx @@ -22,7 +22,11 @@ import { EuiBadge, EuiIcon, EuiTitle, + EuiPopoverFooter, + EuiButtonGroup, + useEuiBackgroundColor, } from '@elastic/eui'; +import { css } from '@emotion/react'; import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public'; import { optionsListReducers } from '../options_list_reducers'; @@ -34,12 +38,23 @@ export interface OptionsListPopoverProps { updateSearchString: (newSearchString: string) => void; } +const aggregationToggleButtons = [ + { + id: 'optionsList__includeResults', + label: OptionsListStrings.popover.getIncludeLabel(), + }, + { + id: 'optionsList__excludeResults', + label: OptionsListStrings.popover.getExcludeLabel(), + }, +]; + export const OptionsListPopover = ({ width, updateSearchString }: OptionsListPopoverProps) => { // Redux embeddable container Context const { useEmbeddableDispatch, useEmbeddableSelector: select, - actions: { selectOption, deselectOption, clearSelections, replaceSelection }, + actions: { selectOption, deselectOption, clearSelections, replaceSelection, setExclude }, } = useReduxEmbeddableContext(); const dispatch = useEmbeddableDispatch(); @@ -52,8 +67,10 @@ export const OptionsListPopover = ({ width, updateSearchString }: OptionsListPop const field = select((state) => state.componentState.field); const selectedOptions = select((state) => state.explicitInput.selectedOptions); + const hideExclude = select((state) => state.explicitInput.hideExclude); const singleSelect = select((state) => state.explicitInput.singleSelect); const title = select((state) => state.explicitInput.title); + const exclude = select((state) => state.explicitInput.exclude); const loading = select((state) => state.output.loading); @@ -65,6 +82,7 @@ export const OptionsListPopover = ({ width, updateSearchString }: OptionsListPop ); const [showOnlySelected, setShowOnlySelected] = useState(false); + const euiBackgroundColor = useEuiBackgroundColor('subdued'); return ( <> @@ -77,6 +95,7 @@ export const OptionsListPopover = ({ width, updateSearchString }: OptionsListPop direction="row" justifyContent="spaceBetween" alignItems="center" + responsive={false} > )}
    + {!hideExclude && ( + + + dispatch(setExclude(optionId === 'optionsList__excludeResults')) + } + buttonSize="compressed" + data-test-subj="optionsList__includeExcludeButtonGroup" + /> + + )} ); }; diff --git a/src/plugins/controls/public/options_list/components/options_list_strings.ts b/src/plugins/controls/public/options_list/components/options_list_strings.ts index 8e3d55ae6b764..cfab9633b81e9 100644 --- a/src/plugins/controls/public/options_list/components/options_list_strings.ts +++ b/src/plugins/controls/public/options_list/components/options_list_strings.ts @@ -18,6 +18,10 @@ export const OptionsListStrings = { i18n.translate('controls.optionsList.control.placeholder', { defaultMessage: 'Any', }), + getNegate: () => + i18n.translate('controls.optionsList.control.negate', { + defaultMessage: 'NOT', + }), }, editor: { getAllowMultiselectTitle: () => @@ -26,7 +30,16 @@ export const OptionsListStrings = { }), getRunPastTimeoutTitle: () => i18n.translate('controls.optionsList.editor.runPastTimeout', { - defaultMessage: 'Run past timeout', + defaultMessage: 'Ignore timeout for results', + }), + getRunPastTimeoutTooltip: () => + i18n.translate('controls.optionsList.editor.runPastTimeout.tooltip', { + defaultMessage: + 'Wait to display results until the list is complete. This setting is useful for large data sets, but the results might take longer to populate.', + }), + getHideExcludeTitle: () => + i18n.translate('controls.optionsList.editor.hideExclude', { + defaultMessage: 'Allow selections to be excluded', }), }, popover: { @@ -86,5 +99,17 @@ export const OptionsListStrings = { '{selectedOptions} selected {selectedOptions, plural, one {option} other {options}} {selectedOptions, plural, one {is} other {are}} ignored because {selectedOptions, plural, one {it is} other {they are}} no longer in the data.', values: { selectedOptions }, }), + getIncludeLabel: () => + i18n.translate('controls.optionsList.popover.includeLabel', { + defaultMessage: 'Include', + }), + getExcludeLabel: () => + i18n.translate('controls.optionsList.popover.excludeLabel', { + defaultMessage: 'Exclude', + }), + getIncludeExcludeLegend: () => + i18n.translate('controls.optionsList.popover.excludeOptionsLegend', { + defaultMessage: 'Include or exclude selections', + }), }, }; diff --git a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx index 292e7cb6b0597..ffead8d9c20bc 100644 --- a/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx +++ b/src/plugins/controls/public/options_list/embeddable/options_list_embeddable.tsx @@ -134,6 +134,7 @@ export class OptionsListEmbeddable extends Embeddable isEqual(a.selectedOptions, b.selectedOptions))) + .pipe( + distinctUntilChanged( + (a, b) => isEqual(a.selectedOptions, b.selectedOptions) && a.exclude === b.exclude + ) + ) .subscribe(async ({ selectedOptions: newSelectedOptions }) => { const { actions: { @@ -364,6 +369,7 @@ export class OptionsListEmbeddable extends Embeddable { const { getState } = this.reduxEmbeddableTools; const { validSelections } = getState().componentState ?? {}; + const { exclude } = this.getInput(); if (!validSelections || isEmpty(validSelections)) { return []; @@ -379,6 +385,7 @@ export class OptionsListEmbeddable extends Embeddable) => { if (state.explicitInput.selectedOptions) state.explicitInput.selectedOptions = []; }, + setExclude: (state: WritableDraft, action: PayloadAction) => { + state.explicitInput.exclude = action.payload; + }, clearValidAndInvalidSelections: (state: WritableDraft) => { state.componentState.invalidSelections = []; state.componentState.validSelections = []; diff --git a/src/plugins/custom_integrations/common/language_integrations.ts b/src/plugins/custom_integrations/common/language_integrations.ts index 9ba914c02fd0d..8a24295096b40 100644 --- a/src/plugins/custom_integrations/common/language_integrations.ts +++ b/src/plugins/custom_integrations/common/language_integrations.ts @@ -37,9 +37,9 @@ export const languageIntegrations: LanguageIntegration[] = [ description: i18n.translate('customIntegrations.languageclients.JavascriptDescription', { defaultMessage: 'Index data to Elasticsearch with the JavaScript client.', }), - docUrlTemplate: `${ELASTICSEARCH_CLIENT_URL}/javascript-api/{branch}/introduction.html`, + docUrlTemplate: '', integrationsAppUrl: `/app/integrations/language_clients/javascript/overview`, - exportLanguageUiComponent: false, + exportLanguageUiComponent: true, }, { id: 'ruby', diff --git a/src/plugins/custom_integrations/public/components/fleet_integration/elasticsearch_js/elasticsearch_js_readme.tsx b/src/plugins/custom_integrations/public/components/fleet_integration/elasticsearch_js/elasticsearch_js_readme.tsx new file mode 100644 index 0000000000000..0202782efe436 --- /dev/null +++ b/src/plugins/custom_integrations/public/components/fleet_integration/elasticsearch_js/elasticsearch_js_readme.tsx @@ -0,0 +1,204 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 or the Server + * Side Public License, v 1. + */ + +import React, { useState } from 'react'; + +// eslint-disable-next-line @kbn/eslint/module_migration +import styled from 'styled-components'; +import cuid from 'cuid'; + +import { + EuiButton, + EuiCode, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiPage, + EuiPageBody, + EuiPageHeader, + EuiPageSection, + EuiSpacer, + EuiText, + EuiTitle, + EuiPanel, + EuiImage, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import icon from '../../../assets/language_clients/nodejs.svg'; + +const CenterColumn = styled(EuiFlexItem)` + max-width: 740px; +`; + +const FixedHeader = styled.div` + width: 100%; + height: 196px; + border-bottom: 1px solid ${euiThemeVars.euiColorLightShade}; +`; + +const IconPanel = styled(EuiPanel)` + padding: ${(props) => props.theme.eui.euiSizeXL}; + width: ${(props) => + parseFloat(props.theme.eui.euiSize) * 6 + parseFloat(props.theme.eui.euiSizeXL) * 2}px; + svg, + img { + height: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px; + width: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px; + } + .euiFlexItem { + height: ${(props) => parseFloat(props.theme.eui.euiSize) * 6}px; + justify-content: center; + } +`; + +const TopFlexGroup = styled(EuiFlexGroup)` + max-width: 1150px; + margin-left: auto; + margin-right: auto; + padding: calc(${euiThemeVars.euiSizeXL} * 2) ${euiThemeVars.euiSizeM} 0 ${euiThemeVars.euiSizeM}; +`; + +export const ElasticsearchJsClientReadme = () => { + const [apiKey, setApiKey] = useState(null); + + return ( + <> + + + + + + + + + +

    + +

    +
    +
    +
    +
    + + + + + + + + + + } + /> + + + + +

    + +

    +
    + + + + + {`# Grab the Elasticsearch JavaScript client from NPM and install it in your project \n`} + {`$ npm install @elastic/elasticsearch@`} + +
    + + + +

    + +

    +
    + + + + + + + + + + setApiKey(cuid())} disabled={!!apiKey}> + Generate API key + + + + {apiKey && ( + + + {apiKey} + + + )} + +
    + + + +

    + +

    +
    + + + index.js, + }} + /> + + + + + + {` +// Import the client +const { Client } = require('@elastic/elasticsearch'); + +// Instantiate the client with an API key +const client = new Client({ + auth: { apiKey: '${apiKey || 'YOUR_API_KEY'}' } +}) + + `} + +
    +
    +
    +
    +
    + + ); +}; diff --git a/src/plugins/custom_integrations/public/components/fleet_integration/sample/sample_client_readme.tsx b/src/plugins/custom_integrations/public/components/fleet_integration/sample/sample_client_readme.tsx index c2ca0d62da689..7b932ca9c99f7 100644 --- a/src/plugins/custom_integrations/public/components/fleet_integration/sample/sample_client_readme.tsx +++ b/src/plugins/custom_integrations/public/components/fleet_integration/sample/sample_client_readme.tsx @@ -81,7 +81,7 @@ export const SampleClientReadme = () => {

    diff --git a/src/plugins/custom_integrations/public/plugin.tsx b/src/plugins/custom_integrations/public/plugin.tsx index 827d31ce3749d..e1e10f327075c 100755 --- a/src/plugins/custom_integrations/public/plugin.tsx +++ b/src/plugins/custom_integrations/public/plugin.tsx @@ -23,6 +23,7 @@ import { import { CustomIntegrationsServicesProvider } from './services'; import { servicesFactory } from './services/kibana'; import { SampleClientReadme } from './components/fleet_integration/sample/sample_client_readme'; +import { ElasticsearchJsClientReadme } from './components/fleet_integration/elasticsearch_js/elasticsearch_js_readme'; export class CustomIntegrationsPlugin implements Plugin @@ -46,7 +47,10 @@ export class CustomIntegrationsPlugin ): CustomIntegrationsStart { const services = servicesFactory({ coreStart, startPlugins }); - const languageClientsUiComponents = { sample: SampleClientReadme }; + const languageClientsUiComponents = { + sample: SampleClientReadme, + javascript: ElasticsearchJsClientReadme, + }; const ContextProvider: React.FC = ({ children }) => ( diff --git a/src/plugins/custom_integrations/server/plugin.test.ts b/src/plugins/custom_integrations/server/plugin.test.ts index 0bfc014ed5cdd..324522a383d83 100644 --- a/src/plugins/custom_integrations/server/plugin.test.ts +++ b/src/plugins/custom_integrations/server/plugin.test.ts @@ -37,8 +37,7 @@ describe('CustomIntegrationsPlugin', () => { description: 'Index data to Elasticsearch with the JavaScript client.', type: 'ui_link', shipper: 'language_clients', - uiInternalPath: - 'https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/branch/introduction.html', + uiInternalPath: '/app/integrations/language_clients/javascript/overview', isBeta: false, icons: [{ type: 'svg', src: undefined }], categories: ['elastic_stack', 'custom', 'language_client'], @@ -150,7 +149,7 @@ describe('CustomIntegrationsPlugin', () => { uiExternalLink: 'https://serverlessrepo.aws.amazon.com/applications/eu-central-1/267093732750/elastic-serverless-forwarder', isBeta: false, - icons: [{ type: 'svg' }], + icons: [{ type: 'svg', src: undefined }], categories: ['aws', 'custom'], }, ]); diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx index b6c3d2055d88c..7ad4b64324507 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx @@ -46,7 +46,7 @@ beforeEach(() => { application = applicationServiceMock.createStartContract(); }); -test('DashboardContainer initializes embeddables', async (done) => { +test('DashboardContainer initializes embeddables', (done) => { const initialInput = getSampleDashboardInput({ panels: { '123': getSampleDashboardPanel({ @@ -96,7 +96,7 @@ test('DashboardContainer.addNewEmbeddable', async () => { expect(embeddableInContainer.id).toBe(embeddable.id); }); -test('DashboardContainer.replacePanel', async (done) => { +test('DashboardContainer.replacePanel', (done) => { const ID = '123'; const initialInput = getSampleDashboardInput({ panels: { @@ -139,7 +139,7 @@ test('DashboardContainer.replacePanel', async (done) => { }); }); -test('Container view mode change propagates to existing children', async (done) => { +test('Container view mode change propagates to existing children', async () => { const initialInput = getSampleDashboardInput({ panels: { '123': getSampleDashboardPanel({ @@ -154,7 +154,6 @@ test('Container view mode change propagates to existing children', async (done) expect(embeddable.getInput().viewMode).toBe(ViewMode.VIEW); container.updateInput({ viewMode: ViewMode.EDIT }); expect(embeddable.getInput().viewMode).toBe(ViewMode.EDIT); - done(); }); test('Container view mode change propagates to new children', async () => { diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx index 41e8b900d360f..6972a521026b4 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx @@ -139,7 +139,7 @@ test.skip('DashboardGrid renders expanded panel', () => { }); // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 -test.skip('DashboardGrid unmount unsubscribes', async (done) => { +test.skip('DashboardGrid unmount unsubscribes', (done) => { const { props } = prepare(); const component = mountWithIntl( diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index 27c86a8ff6c09..a7bad078b56eb 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -160,7 +160,7 @@ test.skip('renders exit full screen button when in full screen mode and empty sc }); // unhandled promise rejection: https://github.com/elastic/kibana/issues/112699 -test.skip('DashboardViewport unmount unsubscribes', async (done) => { +test.skip('DashboardViewport unmount unsubscribes', (done) => { const { props } = getProps(); const component = mount( diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts index 850c6f575904c..6095598ae3788 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_app_state.ts @@ -13,6 +13,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; import { ViewMode } from '@kbn/embeddable-plugin/public'; +import type { DataView } from '@kbn/data-plugin/common'; import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { @@ -225,7 +226,14 @@ export const useDashboardAppState = ({ dashboardContainer.controlGroup?.setRelevantDataViewId(newDataViewIds[0]); } // fetch all data views. These should be cached locally at this time so we will not need to query ES. - const allDataViews = await Promise.all(newDataViewIds.map((id) => dataViews.get(id))); + const responses = await Promise.allSettled(newDataViewIds.map((id) => dataViews.get(id))); + // Keep only fullfilled ones as each panel will handle the rejected ones already on their own + const allDataViews = responses + .filter( + (response): response is PromiseFulfilledResult => + response.status === 'fulfilled' + ) + .map(({ value }) => value); dashboardContainer.setAllDataViews(allDataViews); setDashboardAppState((s) => ({ ...s, dataViews: allDataViews })); }, diff --git a/src/plugins/dashboard/public/application/lib/help_menu_util.ts b/src/plugins/dashboard/public/application/lib/help_menu_util.ts index 9407fa31f545a..d93b2593386ba 100644 --- a/src/plugins/dashboard/public/application/lib/help_menu_util.ts +++ b/src/plugins/dashboard/public/application/lib/help_menu_util.ts @@ -12,9 +12,8 @@ import { pluginServices } from '../../services/plugin_services'; export function addHelpMenuToAppChrome() { const { chrome: { setHelpExtension }, - documentationLinks: { kibanaGuideDocLink }, + documentationLinks: { dashboardDocLink }, } = pluginServices.getServices(); - setHelpExtension({ appName: i18n.translate('dashboard.helpMenu.appName', { defaultMessage: 'Dashboards', @@ -22,7 +21,7 @@ export function addHelpMenuToAppChrome() { links: [ { linkType: 'documentation', - href: `${kibanaGuideDocLink}`, + href: `${dashboardDocLink}`, }, ], }); diff --git a/src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts b/src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts index 3fab07396b4ff..dfdd74bfe4184 100644 --- a/src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts +++ b/src/plugins/dashboard/public/services/documentation_links/documentation_links.stub.ts @@ -18,5 +18,6 @@ export const documentationLinksServiceFactory: DocumentationLinksServiceFactory return { indexPatternsDocLink: corePluginMock.docLinks.links.indexPatterns.introduction, kibanaGuideDocLink: corePluginMock.docLinks.links.kibana.guide, + dashboardDocLink: corePluginMock.docLinks.links.dashboard.guide, }; }; diff --git a/src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts b/src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts index fe00d376675f6..eb65f639d57fe 100644 --- a/src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts +++ b/src/plugins/dashboard/public/services/documentation_links/documentation_links_service.ts @@ -21,14 +21,16 @@ export const documentationLinksServiceFactory: DocumentationLinksServiceFactory const { docLinks: { links: { - kibana: { guide }, + kibana, indexPatterns: { introduction }, + dashboard, }, }, } = coreStart; return { indexPatternsDocLink: introduction, - kibanaGuideDocLink: guide, + kibanaGuideDocLink: kibana.guide, + dashboardDocLink: dashboard.guide, }; }; diff --git a/src/plugins/dashboard/public/services/documentation_links/types.ts b/src/plugins/dashboard/public/services/documentation_links/types.ts index ee7e520471651..d47fbee6ed772 100644 --- a/src/plugins/dashboard/public/services/documentation_links/types.ts +++ b/src/plugins/dashboard/public/services/documentation_links/types.ts @@ -11,4 +11,5 @@ import { CoreStart } from '@kbn/core/public'; export interface DashboardDocumentationLinksService { indexPatternsDocLink: CoreStart['docLinks']['links']['indexPatterns']['introduction']; kibanaGuideDocLink: CoreStart['docLinks']['links']['kibana']['guide']; + dashboardDocLink: CoreStart['docLinks']['links']['dashboard']['guide']; } diff --git a/src/plugins/data/common/search/aggs/agg_configs.test.ts b/src/plugins/data/common/search/aggs/agg_configs.test.ts index cd0495a3f78c6..c3b7ffe937b35 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.test.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.test.ts @@ -16,6 +16,15 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { stubIndexPattern } from '../../stubs'; import { IEsSearchResponse } from '..'; +// Mute moment.tz warnings about not finding a mock timezone +jest.mock('../utils', () => { + const original = jest.requireActual('../utils'); + return { + ...original, + getUserTimeZone: jest.fn(() => 'US/Pacific'), + }; +}); + describe('AggConfigs', () => { const indexPattern: DataView = stubIndexPattern; let typesRegistry: AggTypesRegistryStart; @@ -563,6 +572,82 @@ describe('AggConfigs', () => { '1-bucket>_count' ); }); + + it('prepends a sampling agg whenever sampling is enabled', () => { + const configStates = [ + { + enabled: true, + id: '1', + type: 'avg_bucket', + schema: 'metric', + params: { + customBucket: { + id: '1-bucket', + type: 'date_histogram', + schema: 'bucketAgg', + params: { + field: '@timestamp', + interval: '10s', + }, + }, + customMetric: { + id: '1-metric', + type: 'count', + schema: 'metricAgg', + params: {}, + }, + }, + }, + { + enabled: true, + id: '2', + type: 'terms', + schema: 'bucket', + params: { + field: 'clientip', + }, + }, + { + enabled: true, + id: '3', + type: 'terms', + schema: 'bucket', + params: { + field: 'machine.os.raw', + }, + }, + ]; + + const ac = new AggConfigs( + indexPattern, + configStates, + { typesRegistry, hierarchical: true, probability: 0.5 }, + jest.fn() + ); + const topLevelDsl = ac.toDsl(); + + expect(Object.keys(topLevelDsl)).toContain('sampling'); + expect(Object.keys(topLevelDsl.sampling)).toEqual(['random_sampler', 'aggs']); + expect(Object.keys(topLevelDsl.sampling.aggs)).toContain('2'); + expect(Object.keys(topLevelDsl.sampling.aggs['2'].aggs)).toEqual(['1', '3', '1-bucket']); + }); + + it('should not prepend a sampling agg when no nested agg is avaialble', () => { + const ac = new AggConfigs( + indexPattern, + [ + { + enabled: true, + type: 'count', + schema: 'metric', + }, + ], + { typesRegistry, probability: 0.5 }, + jest.fn() + ); + const topLevelDsl = ac.toDsl(); + expect(Object.keys(topLevelDsl)).not.toContain('sampling'); + }); }); describe('#postFlightTransform', () => { @@ -854,4 +939,74 @@ describe('AggConfigs', () => { `); }); }); + + describe('isSamplingEnabled', () => { + it('should return false if probability is 1', () => { + const ac = new AggConfigs( + indexPattern, + [{ enabled: true, type: 'avg', schema: 'metric', params: { field: 'bytes' } }], + { typesRegistry, probability: 1 }, + jest.fn() + ); + + expect(ac.isSamplingEnabled()).toBeFalsy(); + }); + + it('should return true if probability is less than 1', () => { + const ac = new AggConfigs( + indexPattern, + [{ enabled: true, type: 'avg', schema: 'metric', params: { field: 'bytes' } }], + { typesRegistry, probability: 0.1 }, + jest.fn() + ); + + expect(ac.isSamplingEnabled()).toBeTruthy(); + }); + + it('should return false when all aggs have hasNoDsl flag enabled', () => { + const ac = new AggConfigs( + indexPattern, + [ + { + enabled: true, + type: 'count', + schema: 'metric', + }, + ], + { typesRegistry, probability: 1 }, + jest.fn() + ); + + expect(ac.isSamplingEnabled()).toBeFalsy(); + }); + + it('should return false when no nested aggs are avaialble', () => { + const ac = new AggConfigs( + indexPattern, + [{ enabled: false, type: 'avg', schema: 'metric', params: { field: 'bytes' } }], + { typesRegistry, probability: 1 }, + jest.fn() + ); + + expect(ac.isSamplingEnabled()).toBeFalsy(); + }); + + it('should return true if at least one nested agg is available and probability < 1', () => { + const ac = new AggConfigs( + indexPattern, + [ + { + enabled: true, + type: 'count', + schema: 'metric', + }, + { enabled: true, type: 'avg', schema: 'metric', params: { field: 'bytes' } }, + ], + { typesRegistry, probability: 0.1 }, + jest.fn() + ); + + expect(ac.isSamplingEnabled()).toBeTruthy(); + }); + }); }); diff --git a/src/plugins/data/common/search/aggs/agg_configs.ts b/src/plugins/data/common/search/aggs/agg_configs.ts index c61ca69e0c6df..7cab863fba11d 100644 --- a/src/plugins/data/common/search/aggs/agg_configs.ts +++ b/src/plugins/data/common/search/aggs/agg_configs.ts @@ -26,6 +26,7 @@ import { AggTypesDependencies, GetConfigFn, getUserTimeZone } from '../..'; import { getTime, calculateBounds } from '../..'; import type { IBucketAggConfig } from './buckets'; import { insertTimeShiftSplit, mergeTimeShifts } from './utils/time_splits'; +import { createSamplerAgg, isSamplingEnabled } from './utils/sampler'; function removeParentAggs(obj: any) { for (const prop in obj) { @@ -55,6 +56,8 @@ export interface AggConfigsOptions { hierarchical?: boolean; aggExecutionContext?: AggTypesDependencies['aggExecutionContext']; partialRows?: boolean; + probability?: number; + samplerSeed?: number; } export type CreateAggConfigParams = Assign; @@ -107,6 +110,17 @@ export class AggConfigs { return this.opts.partialRows ?? false; } + public get samplerConfig() { + return { probability: this.opts.probability ?? 1, seed: this.opts.samplerSeed }; + } + + isSamplingEnabled() { + return ( + isSamplingEnabled(this.opts.probability) && + this.getRequestAggs().filter((agg) => !agg.type.hasNoDsl).length > 0 + ); + } + setTimeFields(timeFields: string[] | undefined) { this.timeFields = timeFields; } @@ -225,7 +239,7 @@ export class AggConfigs { } toDsl(): Record { - const dslTopLvl = {}; + const dslTopLvl: Record = {}; let dslLvlCursor: Record; let nestedMetrics: Array<{ config: AggConfig; dsl: Record }> | []; @@ -254,10 +268,21 @@ export class AggConfigs { (config) => 'splitForTimeShift' in config.type && config.type.splitForTimeShift(config, this) ); + if (this.isSamplingEnabled()) { + dslTopLvl.sampling = createSamplerAgg({ + probability: this.opts.probability ?? 1, + seed: this.opts.samplerSeed, + }); + } + requestAggs.forEach((config: AggConfig, i: number, list) => { if (!dslLvlCursor) { // start at the top level dslLvlCursor = dslTopLvl; + // when sampling jump directly to the aggs + if (this.isSamplingEnabled()) { + dslLvlCursor = dslLvlCursor.sampling.aggs; + } } else { const prevConfig: AggConfig = list[i - 1]; const prevDsl = dslLvlCursor[prevConfig.id]; @@ -452,7 +477,12 @@ export class AggConfigs { doc_count: response.rawResponse.hits?.total as estypes.AggregationsAggregate, }; } - const aggCursor = transformedRawResponse.aggregations!; + const aggCursor = this.isSamplingEnabled() + ? (transformedRawResponse.aggregations!.sampling! as Record< + string, + estypes.AggregationsAggregate + >) + : transformedRawResponse.aggregations!; mergeTimeShifts(this, aggCursor); return { @@ -531,6 +561,8 @@ export class AggConfigs { metricsAtAllLevels: this.hierarchical, partialRows: this.partialRows, aggs: this.aggs.map((agg) => buildExpression(agg.toExpressionAst())), + probability: this.opts.probability, + samplerSeed: this.opts.samplerSeed, }), ]).toAst(); } diff --git a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts index 6ce588b98fa9c..be2e969f279b1 100644 --- a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts @@ -18,6 +18,8 @@ import { AggConfigs, CreateAggConfigParams } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketAggConfig } from './bucket_agg_type'; import { mockAggTypesRegistry } from '../test_helpers'; +import { estypes } from '@elastic/elasticsearch'; +import { isSamplingEnabled } from '../utils/sampler'; const indexPattern = { id: '1234', @@ -281,71 +283,63 @@ const nestedOtherResponse = { describe('Terms Agg Other bucket helper', () => { const typesRegistry = mockAggTypesRegistry(); - const getAggConfigs = (aggs: CreateAggConfigParams[] = []) => { - return new AggConfigs(indexPattern, [...aggs], { typesRegistry }, jest.fn()); - }; - - describe('buildOtherBucketAgg', () => { - test('returns a function', () => { - const aggConfigs = getAggConfigs(singleTerm.aggs); - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[0] as IBucketAggConfig, - singleTermResponse - ); - expect(typeof agg).toBe('function'); - }); - - test('correctly builds query with single terms agg', () => { - const aggConfigs = getAggConfigs(singleTerm.aggs); - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[0] as IBucketAggConfig, - singleTermResponse - ); - const expectedResponse = { - aggs: undefined, - filters: { - filters: { - '': { - bool: { - must: [], - filter: [{ exists: { field: 'machine.os.raw' } }], - should: [], - must_not: [ - { match_phrase: { 'machine.os.raw': 'ios' } }, - { match_phrase: { 'machine.os.raw': 'win xp' } }, - ], - }, - }, + for (const probability of [1, 0.5, undefined]) { + function getTitlePostfix() { + if (!isSamplingEnabled(probability)) { + return ''; + } + return ` - with sampling (probability = ${probability})`; + } + function enrichResponseWithSampling(response: any) { + if (!isSamplingEnabled(probability)) { + return response; + } + return { + ...response, + aggregations: { + sampling: { + ...response.aggregations, }, }, }; - expect(agg).toBeDefined(); - if (agg) { - expect(agg()['other-filter']).toEqual(expectedResponse); - } - }); + } - test('correctly builds query for nested terms agg', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[1] as IBucketAggConfig, - nestedTermResponse - ); - const expectedResponse = { - 'other-filter': { + function getAggConfigs(aggs: CreateAggConfigParams[] = []) { + return new AggConfigs(indexPattern, [...aggs], { typesRegistry, probability }, jest.fn()); + } + + function getTopAggregations(updatedResponse: estypes.SearchResponse) { + return !isSamplingEnabled(probability) + ? updatedResponse.aggregations! + : (updatedResponse.aggregations!.sampling as Record); + } + + describe(`buildOtherBucketAgg${getTitlePostfix()}`, () => { + test('returns a function', () => { + const aggConfigs = getAggConfigs(singleTerm.aggs); + const agg = buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[0] as IBucketAggConfig, + enrichResponseWithSampling(singleTermResponse) + ); + expect(typeof agg).toBe('function'); + }); + + test('correctly builds query with single terms agg', () => { + const aggConfigs = getAggConfigs(singleTerm.aggs); + const agg = buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[0] as IBucketAggConfig, + enrichResponseWithSampling(singleTermResponse) + ); + const expectedResponse = { aggs: undefined, filters: { filters: { - [`${SEP}IN-with-dash`]: { + '': { bool: { must: [], - filter: [ - { match_phrase: { 'geo.src': 'IN-with-dash' } }, - { exists: { field: 'machine.os.raw' } }, - ], + filter: [{ exists: { field: 'machine.os.raw' } }], should: [], must_not: [ { match_phrase: { 'machine.os.raw': 'ios' } }, @@ -353,272 +347,322 @@ describe('Terms Agg Other bucket helper', () => { ], }, }, - [`${SEP}US-with-dash`]: { - bool: { - must: [], - filter: [ - { match_phrase: { 'geo.src': 'US-with-dash' } }, - { exists: { field: 'machine.os.raw' } }, - ], - should: [], - must_not: [ - { match_phrase: { 'machine.os.raw': 'ios' } }, - { match_phrase: { 'machine.os.raw': 'win xp' } }, - ], + }, + }, + }; + expect(agg).toBeDefined(); + if (agg) { + const resp = agg(); + const topAgg = !isSamplingEnabled(probability) ? resp : resp.sampling!.aggs; + expect(topAgg['other-filter']).toEqual(expectedResponse); + } + }); + + test('correctly builds query for nested terms agg', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + const agg = buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[1] as IBucketAggConfig, + enrichResponseWithSampling(nestedTermResponse) + ); + const expectedResponse = { + 'other-filter': { + aggs: undefined, + filters: { + filters: { + [`${SEP}IN-with-dash`]: { + bool: { + must: [], + filter: [ + { match_phrase: { 'geo.src': 'IN-with-dash' } }, + { exists: { field: 'machine.os.raw' } }, + ], + should: [], + must_not: [ + { match_phrase: { 'machine.os.raw': 'ios' } }, + { match_phrase: { 'machine.os.raw': 'win xp' } }, + ], + }, + }, + [`${SEP}US-with-dash`]: { + bool: { + must: [], + filter: [ + { match_phrase: { 'geo.src': 'US-with-dash' } }, + { exists: { field: 'machine.os.raw' } }, + ], + should: [], + must_not: [ + { match_phrase: { 'machine.os.raw': 'ios' } }, + { match_phrase: { 'machine.os.raw': 'win xp' } }, + ], + }, }, }, }, }, - }, - }; - expect(agg).toBeDefined(); - if (agg) { - expect(agg()).toEqual(expectedResponse); - } - }); + }; + expect(agg).toBeDefined(); + if (agg) { + const resp = agg(); + const topAgg = !isSamplingEnabled(probability) ? resp : resp.sampling!.aggs; + // console.log({ probability }, JSON.stringify(topAgg, null, 2)); + expect(topAgg).toEqual(expectedResponse); + } + }); - test('correctly builds query for nested terms agg with one disabled', () => { - const oneDisabledNestedTerms = { - aggs: [ - { - id: '2', - type: BUCKET_TYPES.TERMS, - enabled: false, - params: { - field: { - name: 'machine.os.raw', - indexPattern, - filterable: true, + test('correctly builds query for nested terms agg with one disabled', () => { + const oneDisabledNestedTerms = { + aggs: [ + { + id: '2', + type: BUCKET_TYPES.TERMS, + enabled: false, + params: { + field: { + name: 'machine.os.raw', + indexPattern, + filterable: true, + }, + size: 2, + otherBucket: false, + missingBucket: true, }, - size: 2, - otherBucket: false, - missingBucket: true, }, - }, - { - id: '1', - type: BUCKET_TYPES.TERMS, - params: { - field: { - name: 'geo.src', - indexPattern, - filterable: true, + { + id: '1', + type: BUCKET_TYPES.TERMS, + params: { + field: { + name: 'geo.src', + indexPattern, + filterable: true, + }, + size: 2, + otherBucket: true, + missingBucket: false, }, - size: 2, - otherBucket: true, - missingBucket: false, }, - }, - ], - }; - const aggConfigs = getAggConfigs(oneDisabledNestedTerms.aggs); - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[1] as IBucketAggConfig, - singleTermResponse - ); - const expectedResponse = { - 'other-filter': { - aggs: undefined, - filters: { + ], + }; + const aggConfigs = getAggConfigs(oneDisabledNestedTerms.aggs); + const agg = buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[1] as IBucketAggConfig, + enrichResponseWithSampling(singleTermResponse) + ); + const expectedResponse = { + 'other-filter': { + aggs: undefined, filters: { - '': { - bool: { - filter: [ - { - exists: { - field: 'geo.src', + filters: { + '': { + bool: { + filter: [ + { + exists: { + field: 'geo.src', + }, }, - }, - ], - must: [], - must_not: [ - { - match_phrase: { - 'geo.src': 'ios', + ], + must: [], + must_not: [ + { + match_phrase: { + 'geo.src': 'ios', + }, }, - }, - { - match_phrase: { - 'geo.src': 'win xp', + { + match_phrase: { + 'geo.src': 'win xp', + }, }, - }, - ], - should: [], + ], + should: [], + }, }, }, }, }, - }, - }; - expect(agg).toBeDefined(); - if (agg) { - expect(agg()).toEqual(expectedResponse); - } - }); + }; + expect(agg).toBeDefined(); + if (agg) { + const resp = agg(); + const topAgg = !isSamplingEnabled(probability) ? resp : resp.sampling!.aggs; + expect(topAgg).toEqual(expectedResponse); + } + }); - test('does not build query if sum_other_doc_count is 0 (exhaustive terms)', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - expect( - buildOtherBucketAgg( + test('does not build query if sum_other_doc_count is 0 (exhaustive terms)', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + expect( + buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[1] as IBucketAggConfig, + enrichResponseWithSampling(exhaustiveNestedTermResponse) + ) + ).toBeFalsy(); + }); + + test('excludes exists filter for scripted fields', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + aggConfigs.aggs[1].params.field = { + ...aggConfigs.aggs[1].params.field, + scripted: true, + }; + const agg = buildOtherBucketAgg( aggConfigs, aggConfigs.aggs[1] as IBucketAggConfig, - exhaustiveNestedTermResponse - ) - ).toBeFalsy(); - }); - - test('excludes exists filter for scripted fields', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - aggConfigs.aggs[1].params.field.scripted = true; - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[1] as IBucketAggConfig, - nestedTermResponse - ); - const expectedResponse = { - 'other-filter': { - aggs: undefined, - filters: { + enrichResponseWithSampling(nestedTermResponse) + ); + const expectedResponse = { + 'other-filter': { + aggs: undefined, filters: { - [`${SEP}IN-with-dash`]: { - bool: { - must: [], - filter: [{ match_phrase: { 'geo.src': 'IN-with-dash' } }], - should: [], - must_not: [ - { - script: { + filters: { + [`${SEP}IN-with-dash`]: { + bool: { + must: [], + filter: [{ match_phrase: { 'geo.src': 'IN-with-dash' } }], + should: [], + must_not: [ + { script: { - lang: undefined, - params: { value: 'ios' }, - source: '(undefined) == value', + script: { + lang: undefined, + params: { value: 'ios' }, + source: '(undefined) == value', + }, }, }, - }, - { - script: { + { script: { - lang: undefined, - params: { value: 'win xp' }, - source: '(undefined) == value', + script: { + lang: undefined, + params: { value: 'win xp' }, + source: '(undefined) == value', + }, }, }, - }, - ], + ], + }, }, - }, - [`${SEP}US-with-dash`]: { - bool: { - must: [], - filter: [{ match_phrase: { 'geo.src': 'US-with-dash' } }], - should: [], - must_not: [ - { - script: { + [`${SEP}US-with-dash`]: { + bool: { + must: [], + filter: [{ match_phrase: { 'geo.src': 'US-with-dash' } }], + should: [], + must_not: [ + { script: { - lang: undefined, - params: { value: 'ios' }, - source: '(undefined) == value', + script: { + lang: undefined, + params: { value: 'ios' }, + source: '(undefined) == value', + }, }, }, - }, - { - script: { + { script: { - lang: undefined, - params: { value: 'win xp' }, - source: '(undefined) == value', + script: { + lang: undefined, + params: { value: 'win xp' }, + source: '(undefined) == value', + }, }, }, - }, - ], + ], + }, }, }, }, }, - }, - }; - expect(agg).toBeDefined(); - if (agg) { - expect(agg()).toEqual(expectedResponse); - } - }); + }; + expect(agg).toBeDefined(); + if (agg) { + const resp = agg(); + const topAgg = !isSamplingEnabled(probability) ? resp : resp.sampling!.aggs; + expect(topAgg).toEqual(expectedResponse); + } + }); - test('returns false when nested terms agg has no buckets', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - const agg = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[1] as IBucketAggConfig, - nestedTermResponseNoResults - ); + test('returns false when nested terms agg has no buckets', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + const agg = buildOtherBucketAgg( + aggConfigs, + aggConfigs.aggs[1] as IBucketAggConfig, + enrichResponseWithSampling(nestedTermResponseNoResults) + ); - expect(agg).toEqual(false); + expect(agg).toEqual(false); + }); }); - }); - describe('mergeOtherBucketAggResponse', () => { - test('correctly merges other bucket with single terms agg', () => { - const aggConfigs = getAggConfigs(singleTerm.aggs); - const otherAggConfig = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[0] as IBucketAggConfig, - singleTermResponse - ); - - expect(otherAggConfig).toBeDefined(); - if (otherAggConfig) { - const mergedResponse = mergeOtherBucketAggResponse( + describe(`mergeOtherBucketAggResponse${getTitlePostfix()}`, () => { + test('correctly merges other bucket with single terms agg', () => { + const aggConfigs = getAggConfigs(singleTerm.aggs); + const otherAggConfig = buildOtherBucketAgg( aggConfigs, - singleTermResponse, - singleOtherResponse, aggConfigs.aggs[0] as IBucketAggConfig, - otherAggConfig(), - constructSingleTermOtherFilter + enrichResponseWithSampling(singleTermResponse) ); - expect((mergedResponse!.aggregations!['1'] as any).buckets[3].key).toEqual('__other__'); - } - }); - test('correctly merges other bucket with nested terms agg', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - const otherAggConfig = buildOtherBucketAgg( - aggConfigs, - aggConfigs.aggs[1] as IBucketAggConfig, - nestedTermResponse - ); + expect(otherAggConfig).toBeDefined(); + if (otherAggConfig) { + const mergedResponse = mergeOtherBucketAggResponse( + aggConfigs, + enrichResponseWithSampling(singleTermResponse), + enrichResponseWithSampling(singleOtherResponse), + aggConfigs.aggs[0] as IBucketAggConfig, + otherAggConfig(), + constructSingleTermOtherFilter + ); - expect(otherAggConfig).toBeDefined(); - if (otherAggConfig) { - const mergedResponse = mergeOtherBucketAggResponse( + const topAgg = getTopAggregations(mergedResponse); + expect((topAgg['1'] as any).buckets[3].key).toEqual('__other__'); + } + }); + + test('correctly merges other bucket with nested terms agg', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + const otherAggConfig = buildOtherBucketAgg( aggConfigs, - nestedTermResponse, - nestedOtherResponse, aggConfigs.aggs[1] as IBucketAggConfig, - otherAggConfig(), - constructSingleTermOtherFilter + enrichResponseWithSampling(nestedTermResponse) ); - expect((mergedResponse!.aggregations!['1'] as any).buckets[1]['2'].buckets[3].key).toEqual( - '__other__' - ); - } + expect(otherAggConfig).toBeDefined(); + if (otherAggConfig) { + const mergedResponse = mergeOtherBucketAggResponse( + aggConfigs, + enrichResponseWithSampling(nestedTermResponse), + enrichResponseWithSampling(nestedOtherResponse), + aggConfigs.aggs[1] as IBucketAggConfig, + otherAggConfig(), + constructSingleTermOtherFilter + ); + + const topAgg = getTopAggregations(mergedResponse); + expect((topAgg['1'] as any).buckets[1]['2'].buckets[3].key).toEqual('__other__'); + } + }); }); - }); - describe('updateMissingBucket', () => { - test('correctly updates missing bucket key', () => { - const aggConfigs = getAggConfigs(nestedTerm.aggs); - const updatedResponse = updateMissingBucket( - singleTermResponse, - aggConfigs, - aggConfigs.aggs[0] as IBucketAggConfig - ); - expect( - (updatedResponse!.aggregations!['1'] as any).buckets.find( - (bucket: Record) => bucket.key === '__missing__' - ) - ).toBeDefined(); + describe(`updateMissingBucket${getTitlePostfix()}`, () => { + test('correctly updates missing bucket key', () => { + const aggConfigs = getAggConfigs(nestedTerm.aggs); + const updatedResponse = updateMissingBucket( + enrichResponseWithSampling(singleTermResponse), + aggConfigs, + aggConfigs.aggs[0] as IBucketAggConfig + ); + const topAgg = getTopAggregations(updatedResponse); + expect( + (topAgg['1'] as any).buckets.find( + (bucket: Record) => bucket.key === '__missing__' + ) + ).toBeDefined(); + }); }); - }); + } }); diff --git a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts index f695dc1b1d399..68c64f67ef27f 100644 --- a/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts +++ b/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.ts @@ -20,6 +20,7 @@ import { AggGroupNames } from '../agg_groups'; import { IAggConfigs } from '../agg_configs'; import { IAggType } from '../agg_type'; import { IAggConfig } from '../agg_config'; +import { createSamplerAgg } from '../utils/sampler'; export const OTHER_BUCKET_SEPARATOR = '╰┄►'; @@ -128,6 +129,28 @@ const getOtherAggTerms = (requestAgg: Record, key: string, otherAgg .map((filter: Record) => filter.match_phrase[otherAgg.params.field.name]); }; +/** + * Helper function to handle sampling case and get the correct cursor agg from a request object + */ +const getCorrectAggCursorFromRequest = ( + requestAgg: Record, + aggConfigs: IAggConfigs +) => { + return aggConfigs.isSamplingEnabled() ? requestAgg.sampling.aggs : requestAgg; +}; + +/** + * Helper function to handle sampling case and get the correct cursor agg from a response object + */ +const getCorrectAggregationsCursorFromResponse = ( + response: estypes.SearchResponse, + aggConfigs: IAggConfigs +) => { + return aggConfigs.isSamplingEnabled() + ? (response.aggregations?.sampling as Record) + : response.aggregations; +}; + export const buildOtherBucketAgg = ( aggConfigs: IAggConfigs, aggWithOtherBucket: IAggConfig, @@ -234,7 +257,13 @@ export const buildOtherBucketAgg = ( bool: buildQueryFromFilters(filters, indexPattern), }; }; - walkBucketTree(0, response.aggregations, bucketAggs[0].id, [], ''); + walkBucketTree( + 0, + getCorrectAggregationsCursorFromResponse(response, aggConfigs), + bucketAggs[0].id, + [], + '' + ); // bail if there were no bucket results if (noAggBucketResults || exhaustiveBuckets) { @@ -242,6 +271,14 @@ export const buildOtherBucketAgg = ( } return () => { + if (aggConfigs.isSamplingEnabled()) { + return { + sampling: { + ...createSamplerAgg(aggConfigs.samplerConfig), + aggs: { 'other-filter': resultAgg }, + }, + }; + } return { 'other-filter': resultAgg, }; @@ -257,16 +294,27 @@ export const mergeOtherBucketAggResponse = ( otherFilterBuilder: (requestAgg: Record, key: string, otherAgg: IAggConfig) => Filter ): estypes.SearchResponse => { const updatedResponse = cloneDeep(response); - each(otherResponse.aggregations['other-filter'].buckets, (bucket, key) => { + const aggregationsRoot = getCorrectAggregationsCursorFromResponse(otherResponse, aggsConfig); + const updatedAggregationsRoot = getCorrectAggregationsCursorFromResponse( + updatedResponse, + aggsConfig + ); + const buckets = + 'buckets' in aggregationsRoot!['other-filter'] ? aggregationsRoot!['other-filter'].buckets : {}; + each(buckets, (bucket, key) => { if (!bucket.doc_count || key === undefined) return; const bucketKey = key.replace(new RegExp(`^${OTHER_BUCKET_SEPARATOR}`), ''); const aggResultBuckets = getAggResultBuckets( aggsConfig, - updatedResponse.aggregations, + updatedAggregationsRoot, otherAgg, bucketKey ); - const otherFilter = otherFilterBuilder(requestAgg, key, otherAgg); + const otherFilter = otherFilterBuilder( + getCorrectAggCursorFromRequest(requestAgg, aggsConfig), + key, + otherAgg + ); bucket.filters = [otherFilter]; bucket.key = '__other__'; @@ -290,7 +338,10 @@ export const updateMissingBucket = ( agg: IAggConfig ) => { const updatedResponse = cloneDeep(response); - const aggResultBuckets = getAggConfigResultMissingBuckets(updatedResponse.aggregations, agg.id); + const aggResultBuckets = getAggConfigResultMissingBuckets( + getCorrectAggregationsCursorFromResponse(updatedResponse, aggConfigs), + agg.id + ); aggResultBuckets.forEach((bucket) => { bucket.key = '__missing__'; }); diff --git a/src/plugins/data/common/search/aggs/utils/sampler.ts b/src/plugins/data/common/search/aggs/utils/sampler.ts new file mode 100644 index 0000000000000..5a6fde63b0a29 --- /dev/null +++ b/src/plugins/data/common/search/aggs/utils/sampler.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 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 or the Server + * Side Public License, v 1. + */ + +export function createSamplerAgg({ + type = 'random_sampler', + probability, + seed, +}: { + type?: string; + probability: number; + seed?: number; +}) { + return { + [type]: { + probability, + seed, + }, + aggs: {}, + }; +} + +export function isSamplingEnabled(probability: number | undefined) { + return probability != null && probability !== 1; +} diff --git a/src/plugins/data/common/search/aggs/utils/time_splits.ts b/src/plugins/data/common/search/aggs/utils/time_splits.ts index c2fe8aaca0fb2..b1262683446f3 100644 --- a/src/plugins/data/common/search/aggs/utils/time_splits.ts +++ b/src/plugins/data/common/search/aggs/utils/time_splits.ts @@ -427,11 +427,11 @@ export function insertTimeShiftSplit( const timeRange = aggConfigs.timeRange; const filters: Record = {}; const timeField = aggConfigs.timeFields[0]; + const timeFilter = getTime(aggConfigs.indexPattern, timeRange, { + fieldName: timeField, + forceNow: aggConfigs.forceNow, + }) as RangeFilter; Object.entries(timeShifts).forEach(([key, shift]) => { - const timeFilter = getTime(aggConfigs.indexPattern, timeRange, { - fieldName: timeField, - forceNow: aggConfigs.forceNow, - }) as RangeFilter; if (timeFilter) { filters[key] = { range: { diff --git a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts index c8296b3c06557..f0b55de8ffeff 100644 --- a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts +++ b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts @@ -32,6 +32,8 @@ interface Arguments { metricsAtAllLevels?: boolean; partialRows?: boolean; timeFields?: string[]; + probability?: number; + samplerSeed?: number; } export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition< @@ -94,6 +96,21 @@ export const getEsaggsMeta: () => Omit defaultMessage: 'Provide time fields to get the resolved time ranges for the query', }), }, + probability: { + types: ['number'], + default: 1, + help: i18n.translate('data.search.functions.esaggs.probability.help', { + defaultMessage: + 'The probability that a document will be included in the aggregated data. Uses random sampler.', + }), + }, + samplerSeed: { + types: ['number'], + help: i18n.translate('data.search.functions.esaggs.samplerSeed.help', { + defaultMessage: + 'The seed to generate the random sampling of documents. Uses random sampler.', + }), + }, }, }); diff --git a/src/plugins/data/common/search/tabify/tabify.test.ts b/src/plugins/data/common/search/tabify/tabify.test.ts index d7d983fabfdaf..90ef53623c298 100644 --- a/src/plugins/data/common/search/tabify/tabify.test.ts +++ b/src/plugins/data/common/search/tabify/tabify.test.ts @@ -11,184 +11,217 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { AggConfigs, BucketAggParam, IAggConfig, IAggConfigs } from '../aggs'; import { mockAggTypesRegistry } from '../aggs/test_helpers'; import { metricOnly, threeTermBuckets } from './fixtures/fake_hierarchical_data'; +import { isSamplingEnabled } from '../aggs/utils/sampler'; describe('tabifyAggResponse Integration', () => { const typesRegistry = mockAggTypesRegistry(); - const createAggConfigs = (aggs: IAggConfig[] = []) => { - const field = { - name: '@timestamp', - }; - - const indexPattern = { - id: '1234', - title: 'logstash-*', - fields: { - getByName: () => field, - filter: () => [field], - }, - getFormatterForField: () => ({ - toJSON: () => '{}', - }), - } as unknown as DataView; - - return new AggConfigs(indexPattern, aggs, { typesRegistry }, jest.fn()); - }; - - const mockAggConfig = (agg: any): IAggConfig => agg as unknown as IAggConfig; - - test('transforms a simple response properly', () => { - const aggConfigs = createAggConfigs([{ type: 'count' } as any]); - - const resp = tabifyAggResponse(aggConfigs, metricOnly, { - metricsAtAllLevels: true, - }); - - expect(resp).toHaveProperty('rows'); - expect(resp).toHaveProperty('columns'); + for (const probability of [1, 0.5, undefined]) { + function getTitlePostfix() { + if (!isSamplingEnabled(probability)) { + return ''; + } + return ` - with sampling (probability = ${probability})`; + } - expect(resp.rows).toHaveLength(1); - expect(resp.columns).toHaveLength(1); + function enrichResponseWithSampling(response: any) { + if (!isSamplingEnabled(probability)) { + return response; + } + return { + ...response, + aggregations: { + sampling: { + ...response.aggregations, + }, + }, + }; + } - expect(resp.rows[0]).toEqual({ 'col-0-1': 1000 }); - expect(resp.columns[0]).toHaveProperty('name', aggConfigs.aggs[0].makeLabel()); + const createAggConfigs = (aggs: IAggConfig[] = []) => { + const field = { + name: '@timestamp', + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + getFormatterForField: () => ({ + toJSON: () => '{}', + }), + } as unknown as DataView; + + return new AggConfigs(indexPattern, aggs, { typesRegistry, probability }, jest.fn()); + }; - expect(resp).toHaveProperty('meta.type', 'esaggs'); - expect(resp).toHaveProperty('meta.source', '1234'); - expect(resp).toHaveProperty('meta.statistics.totalCount', 1000); - }); + const mockAggConfig = (agg: any): IAggConfig => agg as unknown as IAggConfig; - describe('scaleMetricValues performance check', () => { - beforeAll(() => { - typesRegistry.get('count').params.push({ - name: 'scaleMetricValues', - default: false, - write: () => {}, - advanced: true, - } as any as BucketAggParam); - }); - test('does not call write if scaleMetricValues is not set', () => { + test(`transforms a simple response properly${getTitlePostfix()}`, () => { const aggConfigs = createAggConfigs([{ type: 'count' } as any]); - const writeMock = jest.fn(); - aggConfigs.getRequestAggs()[0].write = writeMock; - - tabifyAggResponse(aggConfigs, metricOnly, { + const resp = tabifyAggResponse(aggConfigs, enrichResponseWithSampling(metricOnly), { metricsAtAllLevels: true, }); - expect(writeMock).not.toHaveBeenCalled(); - }); - test('does call write if scaleMetricValues is set', () => { - const aggConfigs = createAggConfigs([ - { type: 'count', params: { scaleMetricValues: true } } as any, - ]); + expect(resp).toHaveProperty('rows'); + expect(resp).toHaveProperty('columns'); - const writeMock = jest.fn(() => ({})); - aggConfigs.getRequestAggs()[0].write = writeMock; - - tabifyAggResponse(aggConfigs, metricOnly, { - metricsAtAllLevels: true, - }); - expect(writeMock).toHaveBeenCalled(); - }); - }); - - describe('transforms a complex response', () => { - let esResp: typeof threeTermBuckets; - let aggConfigs: IAggConfigs; - let avg: IAggConfig; - let ext: IAggConfig; - let src: IAggConfig; - let os: IAggConfig; - - beforeEach(() => { - aggConfigs = createAggConfigs([ - mockAggConfig({ type: 'avg', schema: 'metric', params: { field: '@timestamp' } }), - mockAggConfig({ type: 'terms', schema: 'split', params: { field: '@timestamp' } }), - mockAggConfig({ type: 'terms', schema: 'segment', params: { field: '@timestamp' } }), - mockAggConfig({ type: 'terms', schema: 'segment', params: { field: '@timestamp' } }), - ]); - - [avg, ext, src, os] = aggConfigs.aggs; - - esResp = threeTermBuckets; - esResp.aggregations.agg_2.buckets[1].agg_3.buckets[0].agg_4.buckets = []; - }); + expect(resp.rows).toHaveLength(1); + expect(resp.columns).toHaveLength(1); - // check that the columns of a table are formed properly - function expectColumns(table: ReturnType, aggs: IAggConfig[]) { - expect(table.columns).toHaveLength(aggs.length); + expect(resp.rows[0]).toEqual({ 'col-0-1': 1000 }); + expect(resp.columns[0]).toHaveProperty('name', aggConfigs.aggs[0].makeLabel()); - aggs.forEach((agg, i) => { - expect(table.columns[i]).toHaveProperty('name', agg.makeLabel()); - }); - } + expect(resp).toHaveProperty('meta.type', 'esaggs'); + expect(resp).toHaveProperty('meta.source', '1234'); + expect(resp).toHaveProperty('meta.statistics.totalCount', 1000); + }); - // check that a row has expected values - function expectRow( - row: Record, - asserts: Array<(val: string | number) => void> - ) { - expect(typeof row).toBe('object'); - - asserts.forEach((assert, i: number) => { - if (row[`col-${i}`]) { - assert(row[`col-${i}`]); - } + describe(`scaleMetricValues performance check${getTitlePostfix()}`, () => { + beforeAll(() => { + typesRegistry.get('count').params.push({ + name: 'scaleMetricValues', + default: false, + write: () => {}, + advanced: true, + } as any as BucketAggParam); }); - } + test('does not call write if scaleMetricValues is not set', () => { + const aggConfigs = createAggConfigs([{ type: 'count' } as any]); - // check for two character country code - function expectCountry(val: string | number) { - expect(typeof val).toBe('string'); - expect(val).toHaveLength(2); - } + const writeMock = jest.fn(); + aggConfigs.getRequestAggs()[0].write = writeMock; - // check for an OS term - function expectExtension(val: string | number) { - expect(val).toMatch(/^(js|png|html|css|jpg)$/); - } + tabifyAggResponse(aggConfigs, enrichResponseWithSampling(metricOnly), { + metricsAtAllLevels: true, + }); + expect(writeMock).not.toHaveBeenCalled(); + }); - // check for an OS term - function expectOS(val: string | number) { - expect(val).toMatch(/^(win|mac|linux)$/); - } + test('does call write if scaleMetricValues is set', () => { + const aggConfigs = createAggConfigs([ + { type: 'count', params: { scaleMetricValues: true } } as any, + ]); - // check for something like an average bytes result - function expectAvgBytes(val: string | number) { - expect(typeof val).toBe('number'); - expect(val === 0 || val > 1000).toBeDefined(); - } + const writeMock = jest.fn(() => ({})); + aggConfigs.getRequestAggs()[0].write = writeMock; - test('for non-hierarchical vis', () => { - // the default for a non-hierarchical vis is to display - // only complete rows, and only put the metrics at the end. + tabifyAggResponse(aggConfigs, enrichResponseWithSampling(metricOnly), { + metricsAtAllLevels: true, + }); + expect(writeMock).toHaveBeenCalled(); + }); + }); - const tabbed = tabifyAggResponse(aggConfigs, esResp, { metricsAtAllLevels: false }); + describe(`transforms a complex response${getTitlePostfix()}`, () => { + let esResp: typeof threeTermBuckets; + let aggConfigs: IAggConfigs; + let avg: IAggConfig; + let ext: IAggConfig; + let src: IAggConfig; + let os: IAggConfig; + + function getTopAggregations( + rawResp: typeof threeTermBuckets + ): typeof threeTermBuckets['aggregations'] { + return !isSamplingEnabled(probability) + ? rawResp.aggregations! + : // @ts-ignore + rawResp.aggregations!.sampling!; + } + + beforeEach(() => { + aggConfigs = createAggConfigs([ + mockAggConfig({ type: 'avg', schema: 'metric', params: { field: '@timestamp' } }), + mockAggConfig({ type: 'terms', schema: 'split', params: { field: '@timestamp' } }), + mockAggConfig({ type: 'terms', schema: 'segment', params: { field: '@timestamp' } }), + mockAggConfig({ type: 'terms', schema: 'segment', params: { field: '@timestamp' } }), + ]); - expectColumns(tabbed, [ext, src, os, avg]); + [avg, ext, src, os] = aggConfigs.aggs; - tabbed.rows.forEach((row) => { - expectRow(row, [expectExtension, expectCountry, expectOS, expectAvgBytes]); + esResp = enrichResponseWithSampling(threeTermBuckets); + getTopAggregations(esResp).agg_2.buckets[1].agg_3.buckets[0].agg_4.buckets = []; }); - }); - - test('for hierarchical vis', () => { - const tabbed = tabifyAggResponse(aggConfigs, esResp, { metricsAtAllLevels: true }); - expectColumns(tabbed, [ext, avg, src, avg, os, avg]); + // check that the columns of a table are formed properly + function expectColumns(table: ReturnType, aggs: IAggConfig[]) { + expect(table.columns).toHaveLength(aggs.length); + + aggs.forEach((agg, i) => { + expect(table.columns[i]).toHaveProperty('name', agg.makeLabel()); + }); + } + + // check that a row has expected values + function expectRow( + row: Record, + asserts: Array<(val: string | number) => void> + ) { + expect(typeof row).toBe('object'); + + asserts.forEach((assert, i: number) => { + if (row[`col-${i}`]) { + assert(row[`col-${i}`]); + } + }); + } + + // check for two character country code + function expectCountry(val: string | number) { + expect(typeof val).toBe('string'); + expect(val).toHaveLength(2); + } + + // check for an OS term + function expectExtension(val: string | number) { + expect(val).toMatch(/^(js|png|html|css|jpg)$/); + } + + // check for an OS term + function expectOS(val: string | number) { + expect(val).toMatch(/^(win|mac|linux)$/); + } + + // check for something like an average bytes result + function expectAvgBytes(val: string | number) { + expect(typeof val).toBe('number'); + expect(val === 0 || val > 1000).toBeDefined(); + } + + test('for non-hierarchical vis', () => { + // the default for a non-hierarchical vis is to display + // only complete rows, and only put the metrics at the end. + + const tabbed = tabifyAggResponse(aggConfigs, esResp, { metricsAtAllLevels: false }); + + expectColumns(tabbed, [ext, src, os, avg]); + + tabbed.rows.forEach((row) => { + expectRow(row, [expectExtension, expectCountry, expectOS, expectAvgBytes]); + }); + }); - tabbed.rows.forEach((row) => { - expectRow(row, [ - expectExtension, - expectAvgBytes, - expectCountry, - expectAvgBytes, - expectOS, - expectAvgBytes, - ]); + test('for hierarchical vis', () => { + const tabbed = tabifyAggResponse(aggConfigs, esResp, { metricsAtAllLevels: true }); + + expectColumns(tabbed, [ext, avg, src, avg, os, avg]); + + tabbed.rows.forEach((row) => { + expectRow(row, [ + expectExtension, + expectAvgBytes, + expectCountry, + expectAvgBytes, + expectOS, + expectAvgBytes, + ]); + }); }); }); - }); + } }); diff --git a/src/plugins/data/common/search/tabify/tabify.ts b/src/plugins/data/common/search/tabify/tabify.ts index ea26afd30129a..2c332c1ad6a75 100644 --- a/src/plugins/data/common/search/tabify/tabify.ts +++ b/src/plugins/data/common/search/tabify/tabify.ts @@ -147,7 +147,9 @@ export function tabifyAggResponse( const write = new TabbedAggResponseWriter(aggConfigs, respOpts || {}); const topLevelBucket: AggResponseBucket = { - ...esResponse.aggregations, + ...(aggConfigs.isSamplingEnabled() + ? esResponse.aggregations.sampling + : esResponse.aggregations), doc_count: esResponse.aggregations?.doc_count || esResponse.hits?.total, }; diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts index 7c86581b42a6b..e0cd902836df6 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_mapping_chain.test.ts @@ -86,7 +86,7 @@ describe('filter manager utilities', () => { expect(result).toEqual({ key: 'test', value: 'example' }); }); - test('should throw an error if no functions match', async (done) => { + test('should throw an error if no functions match', async () => { const filter = buildEmptyFilter(true); mapping.throws(filter); @@ -98,7 +98,6 @@ describe('filter manager utilities', () => { } catch (err) { expect(err).toBeInstanceOf(Error); expect(err.message).toBe('No mappings have been found for filter.'); - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts index ff9c6d47660fd..e0bc31787df0f 100644 --- a/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/map_filter.test.ts @@ -54,7 +54,7 @@ describe('filter manager utilities', () => { expect(after.meta).toHaveProperty('negate', false); }); - test('should finish with a catch', async (done) => { + test('should finish with a catch', async () => { const before: any = { meta: { index: 'logstash-*' } }; try { @@ -62,8 +62,6 @@ describe('filter manager utilities', () => { } catch (e) { expect(e).toBeInstanceOf(Error); expect(e.message).toBe('No mappings have been found for filter.'); - - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts index 0e642b3f99dcc..b46f5b4c488e3 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_exists.test.ts @@ -29,14 +29,13 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('value', 'exists'); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', async () => { const filter = buildEmptyFilter(true); try { mapQueryString(filter); } catch (e) { expect(e).toBe(filter); - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts index 88e819bb7f9a7..93d5547f1b9e6 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.test.ts @@ -23,7 +23,7 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('key', '_type'); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', async () => { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, @@ -33,7 +33,6 @@ describe('filter manager utilities', () => { mapPhrase(filter); } catch (e) { expect(e).toBe(filter); - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts index 4a219e23aff09..6c69f0ff948d9 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.test.ts @@ -29,7 +29,7 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('value', ['hello', 1, 'world']); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', (done) => { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts index f54cea3164919..df5347c33e6a0 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_query_string.test.ts @@ -19,14 +19,13 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('value', 'foo:bar'); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', async () => { const filter = buildEmptyFilter(true); try { mapQueryString(filter as Filter); } catch (e) { expect(e).toBe(filter); - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts index 065e3da189999..82c701a510dfa 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.test.ts @@ -22,7 +22,7 @@ describe('filter manager utilities', () => { expect(result).toHaveProperty('value', { gt: 1024, lt: 2048 }); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', async () => { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } }, @@ -32,8 +32,6 @@ describe('filter manager utilities', () => { mapRange(filter); } catch (e) { expect(e).toBe(filter); - - done(); } }); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts index 9065ccfa17856..67dd343dd33c4 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_spatial_filter.test.ts @@ -109,7 +109,7 @@ describe('mapSpatialFilter()', () => { expect(result).toHaveProperty('type', FILTERS.SPATIAL_FILTER); }); - test('should return undefined for none matching', async (done) => { + test('should return undefined for none matching', async () => { const filter = { meta: { key: 'location', @@ -124,8 +124,6 @@ describe('mapSpatialFilter()', () => { mapSpatialFilter(filter); } catch (e) { expect(e).toBe(filter); - - done(); } }); }); diff --git a/src/plugins/data/public/query/timefilter/lib/auto_refresh_loop.test.ts b/src/plugins/data/public/query/timefilter/lib/auto_refresh_loop.test.ts index 3c8b316c3b878..fc30a120c445f 100644 --- a/src/plugins/data/public/query/timefilter/lib/auto_refresh_loop.test.ts +++ b/src/plugins/data/public/query/timefilter/lib/auto_refresh_loop.test.ts @@ -8,7 +8,7 @@ import { createAutoRefreshLoop, AutoRefreshDoneFn } from './auto_refresh_loop'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); test('triggers refresh with interval', () => { const { loop$, start, stop } = createAutoRefreshLoop(); diff --git a/src/plugins/data/public/query/timefilter/timefilter.test.ts b/src/plugins/data/public/query/timefilter/timefilter.test.ts index 0152076c7b8a6..7ba6f07835f7b 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.test.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); import sinon from 'sinon'; import moment from 'moment'; diff --git a/src/plugins/data/public/search/expressions/esaggs.test.ts b/src/plugins/data/public/search/expressions/esaggs.test.ts index aa26ceab9ae6b..1a04e4ffeb839 100644 --- a/src/plugins/data/public/search/expressions/esaggs.test.ts +++ b/src/plugins/data/public/search/expressions/esaggs.test.ts @@ -50,6 +50,7 @@ describe('esaggs expression function - public', () => { metricsAtAllLevels: true, partialRows: false, timeFields: ['@timestamp', 'utc_time'], + probability: 1, }; beforeEach(() => { @@ -88,7 +89,7 @@ describe('esaggs expression function - public', () => { expect(startDependencies.aggs.createAggConfigs).toHaveBeenCalledWith( {}, args.aggs.map((agg) => agg.value), - { hierarchical: true, partialRows: false } + { hierarchical: true, partialRows: false, probability: 1, samplerSeed: undefined } ); }); @@ -98,6 +99,8 @@ describe('esaggs expression function - public', () => { expect(startDependencies.aggs.createAggConfigs).toHaveBeenCalledWith({}, [], { hierarchical: true, partialRows: false, + probability: 1, + samplerSeed: undefined, }); }); diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index d944dcd5c1a5d..b82401d4d8caf 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -48,7 +48,12 @@ export function getFunctionDefinition({ const aggConfigs = aggs.createAggConfigs( indexPattern, args.aggs?.map((agg) => agg.value) ?? [], - { hierarchical: args.metricsAtAllLevels, partialRows: args.partialRows } + { + hierarchical: args.metricsAtAllLevels, + partialRows: args.partialRows, + probability: args.probability, + samplerSeed: args.samplerSeed, + } ); const { handleEsaggsRequest } = await import('../../../common/search/expressions'); diff --git a/src/plugins/data/public/search/search_interceptor/search_abort_controller.test.ts b/src/plugins/data/public/search/search_interceptor/search_abort_controller.test.ts index 5d9d8a9903325..82d917216f306 100644 --- a/src/plugins/data/public/search/search_interceptor/search_abort_controller.test.ts +++ b/src/plugins/data/public/search/search_interceptor/search_abort_controller.test.ts @@ -10,7 +10,7 @@ import { SearchAbortController } from './search_abort_controller'; const timeTravel = (msToRun = 0) => { jest.advanceTimersByTime(msToRun); - return new Promise((resolve) => setImmediate(resolve)); + return new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); }; describe('search abort controller', () => { @@ -75,7 +75,7 @@ describe('search abort controller', () => { describe('timeout abort', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts index 35bbecfdb5c4d..73ce6672064aa 100644 --- a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts @@ -41,8 +41,10 @@ let mockCoreSetup: MockedKeys; let bfetchSetup: jest.Mocked; let fetchMock: jest.Mock; -const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); -jest.useFakeTimers(); +const flushPromises = () => + new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); + +jest.useFakeTimers('legacy'); const timeTravel = async (msToRun = 0) => { await flushPromises(); @@ -1531,7 +1533,7 @@ describe('SearchInterceptor', () => { await flushPromises(); }); - test('Immediately aborts if passed an aborted abort signal', async (done) => { + test('Immediately aborts if passed an aborted abort signal', async () => { const abort = new AbortController(); const mockRequest: IEsSearchRequest = { params: {}, @@ -1542,7 +1544,6 @@ describe('SearchInterceptor', () => { error.mockImplementation((e) => { expect(e).toBeInstanceOf(AbortError); expect(fetchMock).not.toBeCalled(); - done(); }); response.subscribe({ error }); diff --git a/src/plugins/data/public/search/session/session_helpers.test.ts b/src/plugins/data/public/search/session/session_helpers.test.ts index bc092a4f6ac3f..8d33d58b55acc 100644 --- a/src/plugins/data/public/search/session/session_helpers.test.ts +++ b/src/plugins/data/public/search/session/session_helpers.test.ts @@ -65,7 +65,7 @@ beforeEach(() => { describe('waitUntilNextSessionCompletes$', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { jest.useRealTimers(); diff --git a/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx b/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx index 9f29e2866fb61..1174142db213f 100644 --- a/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx +++ b/src/plugins/data/public/search/session/session_indicator/connected_search_session_indicator/connected_search_session_indicator.test.tsx @@ -226,7 +226,7 @@ describe('Completed inactivity', () => { describe('tour steps', () => { describe('loading state', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx b/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx index ac554af701d04..be398ba7294fc 100644 --- a/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx +++ b/src/plugins/data/public/search/session/sessions_mgmt/components/table/table.test.tsx @@ -152,7 +152,7 @@ describe('Background Search Session Management Table', () => { // FLAKY: https://github.com/elastic/kibana/issues/88928 describe.skip('fetching sessions data', () => { test('re-fetches data', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); sessionsClient.find = jest.fn(); mockConfig = { ...mockConfig, diff --git a/src/plugins/data/server/lib/get_request_aborted_signal.test.ts b/src/plugins/data/server/lib/get_request_aborted_signal.test.ts index 3a94d020de4da..eb0c590c9c7bc 100644 --- a/src/plugins/data/server/lib/get_request_aborted_signal.test.ts +++ b/src/plugins/data/server/lib/get_request_aborted_signal.test.ts @@ -10,7 +10,7 @@ import { Subject } from 'rxjs'; import { getRequestAbortedSignal } from './get_request_aborted_signal'; describe('abortableRequestHandler', () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); it('should call abort if disconnected', () => { const abortedSubject = new Subject(); diff --git a/src/plugins/data/server/search/expressions/esaggs.test.ts b/src/plugins/data/server/search/expressions/esaggs.test.ts index 166f2719e356d..9954fb2457968 100644 --- a/src/plugins/data/server/search/expressions/esaggs.test.ts +++ b/src/plugins/data/server/search/expressions/esaggs.test.ts @@ -51,6 +51,7 @@ describe('esaggs expression function - server', () => { metricsAtAllLevels: true, partialRows: false, timeFields: ['@timestamp', 'utc_time'], + probability: 1, }; beforeEach(() => { diff --git a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts index d8413703808c7..15a6a4df7eed8 100644 --- a/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/es_search/es_search_strategy.test.ts @@ -58,7 +58,7 @@ describe('ES search strategy', () => { expect(typeof esSearch.search).toBe('function'); }); - it('calls the API caller with the params with defaults', async (done) => { + it('calls the API caller with the params with defaults', async () => { const params = { index: 'logstash-*' }; await esSearchStrategyProvider(mockConfig$, mockLogger) @@ -70,11 +70,10 @@ describe('ES search strategy', () => { ignore_unavailable: true, track_total_hits: true, }); - done(); }); }); - it('calls the API caller with overridden defaults', async (done) => { + it('calls the API caller with overridden defaults', async () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; await esSearchStrategyProvider(mockConfig$, mockLogger) @@ -85,11 +84,10 @@ describe('ES search strategy', () => { ...params, track_total_hits: true, }); - done(); }); }); - it('has all response parameters', async (done) => + it('has all response parameters', async () => await esSearchStrategyProvider(mockConfig$, mockLogger) .search( { @@ -103,7 +101,6 @@ describe('ES search strategy', () => { expect(data.isPartial).toBe(false); expect(data).toHaveProperty('loaded'); expect(data).toHaveProperty('rawResponse'); - done(); })); it('calls the client with transport options', async () => { @@ -137,7 +134,7 @@ describe('ES search strategy', () => { expect(esClient.search.mock.calls[0][1]).toEqual({ signal: expect.any(AbortSignal) }); }); - it('throws normalized error if ResponseError is thrown', async (done) => { + it('throws normalized error if ResponseError is thrown', async () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; const errResponse = new errors.ResponseError({ body: indexNotFoundException, @@ -157,11 +154,10 @@ describe('ES search strategy', () => { expect(e.statusCode).toBe(404); expect(e.message).toBe(errResponse.message); expect(e.errBody).toBe(indexNotFoundException); - done(); } }); - it('throws normalized error if ElasticsearchClientError is thrown', async (done) => { + it('throws normalized error if ElasticsearchClientError is thrown', async () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; const errResponse = new errors.ElasticsearchClientError('This is a general ESClient error'); @@ -175,11 +171,10 @@ describe('ES search strategy', () => { expect(e.statusCode).toBe(500); expect(e.message).toBe(errResponse.message); expect(e.errBody).toBe(undefined); - done(); } }); - it('throws normalized error if ESClient throws unknown error', async (done) => { + it('throws normalized error if ESClient throws unknown error', async () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; const errResponse = new Error('ESClient error'); @@ -193,11 +188,10 @@ describe('ES search strategy', () => { expect(e.statusCode).toBe(500); expect(e.message).toBe(errResponse.message); expect(e.errBody).toBe(undefined); - done(); } }); - it('throws KbnServerError for unknown index type', async (done) => { + it('throws KbnServerError for unknown index type', async () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; try { @@ -210,7 +204,6 @@ describe('ES search strategy', () => { expect(e.message).toBe('Unsupported index pattern type banana'); expect(e.statusCode).toBe(400); expect(e.errBody).toBe(undefined); - done(); } }); }); diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index a1ac225ba4b38..8d666590b3d30 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -346,8 +346,18 @@ export function getUiSettings( "to": "now" }`, type: 'json', - description: i18n.translate('data.advancedSettings.timepicker.timeDefaultsText', { - defaultMessage: 'The timefilter selection to use when Kibana is started without one', + description: i18n.translate('data.advancedSettings.timepicker.timeDefaultsDescription', { + defaultMessage: + 'The timefilter selection to use when Kibana is started without one. Must be an object containing "from" and "to" (see {acceptedFormatsLink}).', + values: { + acceptedFormatsLink: + `` + + i18n.translate('data.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText', { + defaultMessage: 'accepted formats', + }) + + '', + }, }), requiresPageReload: true, schema: schema.object({ diff --git a/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx b/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx index 9c86170795961..e9b2b8de5c5f7 100644 --- a/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx +++ b/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx @@ -335,7 +335,7 @@ const IndexPatternEditorFlyoutContentComponent = ({ })} > {i18n.translate('indexPatternEditor.goToManagementPage', { - defaultMessage: 'View on data view management page', + defaultMessage: 'Manage settings and view field details', })} )} diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx index 50dce25679252..825402fd00a24 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.test.tsx @@ -65,7 +65,7 @@ describe('', () => { }; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.test.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.test.ts index 51cd024f0b53e..094c38e0eb715 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.test.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.test.ts @@ -18,7 +18,7 @@ describe('', () => { const { httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts index 8659e12909763..66cf2bab43d3d 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.test.ts @@ -27,7 +27,7 @@ describe('Field editor Preview panel', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts b/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts index e39642ac2bce2..0d0f1f0802bff 100644 --- a/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts +++ b/src/plugins/discover/public/application/main/utils/get_fetch_observeable.test.ts @@ -51,7 +51,7 @@ describe('getFetchObservable', () => { jest.useRealTimers(); }); - test('refetch$.next should trigger fetch$.next', async (done) => { + test('refetch$.next should trigger fetch$.next', (done) => { const searchSessionManagerMock = createSearchSessionMock(); const main$ = new BehaviorSubject({ fetchStatus: FetchStatus.UNINITIALIZED }); @@ -75,7 +75,7 @@ describe('getFetchObservable', () => { test( 'getAutoRefreshFetch$ should trigger fetch$.next', fakeSchedulers((advance) => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const searchSessionManagerMock = createSearchSessionMock(); const autoRefreshFetch$ = new Subject(); diff --git a/src/plugins/embeddable/README.md b/src/plugins/embeddable/README.md index 14fab2f8412f3..eae9ef04cfb9b 100644 --- a/src/plugins/embeddable/README.md +++ b/src/plugins/embeddable/README.md @@ -90,7 +90,6 @@ export class HelloWorldEmbeddableFactoryDefinition implements EmbeddableFactoryD The embeddable should implement the `IEmbeddable` interface, and usually, that just extends the base class `Embeddable`. ```tsx import React from 'react'; -import { render } from 'react-dom'; import { Embeddable } from '@kbn/embeddable-plugin/public'; export const HELLO_WORLD = 'HELLO_WORLD'; @@ -98,8 +97,8 @@ export const HELLO_WORLD = 'HELLO_WORLD'; export class HelloWorld extends Embeddable { readonly type = HELLO_WORLD; - render(node: HTMLElement) { - render(
    {this.getTitle()}
    , node); + render() { + return
    {this.getTitle()}
    ; } reload() {} @@ -126,6 +125,21 @@ export class HelloWorld extends Embeddable { } ``` +There is also an option to return a [React node](https://reactjs.org/docs/react-component.html#render) directly. +In that case, the returned node will be automatically mounted and unmounted. +```tsx +import React from 'react'; +import { Embeddable } from '@kbn/embeddable-plugin/public'; + +export class HelloWorld extends Embeddable { + // ... + + render() { + return
    {this.getTitle()}
    ; + } +} +``` + #### `reload` This hook is called after every input update to perform some UI changes. ```typescript @@ -150,13 +164,13 @@ export class HelloWorld extends Embeddable { } ``` -#### `renderError` +#### `catchError` This is an optional error handler to provide a custom UI for the error state. The embeddable may change its state in the future so that the error should be able to disappear. In that case, the method should return a callback performing cleanup actions for the error UI. -If there is no implementation provided for the `renderError` hook, the embeddable will render a fallback error UI. +If there is no implementation provided for the `catchError` hook, the embeddable will render a fallback error UI. In case of an error, the embeddable UI will not be destroyed or unmounted. The default behavior is to hide that visually and show the error message on top of that. @@ -169,7 +183,7 @@ import { Embeddable } from '@kbn/embeddable-plugin/public'; export class HelloWorld extends Embeddable { // ... - renderError(node: HTMLElement, error: Error) { + catchError(error: Error, node: HTMLElement) { render(
    Something went wrong: {error.message}
    , node); return () => unmountComponentAtNode(node); @@ -177,6 +191,21 @@ export class HelloWorld extends Embeddable { } ``` +There is also an option to return a [React node](https://reactjs.org/docs/react-component.html#render) directly. +In that case, the returned node will be automatically mounted and unmounted. +```typescript +import React from 'react'; +import { Embeddable } from '@kbn/embeddable-plugin/public'; + +export class HelloWorld extends Embeddable { + // ... + + catchError(error: Error) { + return
    Something went wrong: {error.message}
    ; + } +} +``` + #### `destroy` This hook is invoked when the embeddable is destroyed and should perform cleanup actions. ```typescript @@ -366,7 +395,6 @@ To perform state mutations, the plugin also exposes a pre-defined state of the a Here is an example of initializing a Redux store: ```tsx import React from 'react'; -import { render } from 'react-dom'; import { connect, Provider } from 'react-redux'; import { Embeddable, IEmbeddable } from '@kbn/embeddable-plugin/public'; import { createStore, State } from '@kbn/embeddable-plugin/public/store'; @@ -381,16 +409,15 @@ export class HelloWorld extends Embeddable { reload() {} - render(node: HTMLElement) { + render() { const Component = connect((state: State) => ({ title: state.input.title }))( HelloWorldComponent ); - render( + return ( - , - node + ); } } @@ -434,7 +461,6 @@ That means there is no need to reimplement already existing actions. ```tsx import React from 'react'; -import { render } from 'react-dom'; import { createSlice } from '@reduxjs/toolkit'; import { Embeddable, @@ -523,7 +549,6 @@ This can be achieved by passing a custom reducer. ```tsx import React from 'react'; -import { render } from 'react-dom'; import { createSlice } from '@reduxjs/toolkit'; import { Embeddable, IEmbeddable } from '@kbn/embeddable-plugin/public'; import { createStore, State } from '@kbn/embeddable-plugin/public/store'; diff --git a/src/plugins/embeddable/public/__stories__/embeddable_panel.stories.tsx b/src/plugins/embeddable/public/__stories__/embeddable_panel.stories.tsx index 97e8b83b9f7b1..c7fba909068da 100644 --- a/src/plugins/embeddable/public/__stories__/embeddable_panel.stories.tsx +++ b/src/plugins/embeddable/public/__stories__/embeddable_panel.stories.tsx @@ -15,7 +15,6 @@ import React, { useMemo, useRef, } from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; import { ReplaySubject } from 'rxjs'; import { ThemeContext } from '@emotion/react'; import { DecoratorFn, Meta } from '@storybook/react'; @@ -251,15 +250,13 @@ DefaultWithError.argTypes = { export function DefaultWithCustomError({ message, ...props }: DefaultWithErrorProps) { const ref = useRef>(null); - useEffect( - () => - ref.current?.embeddable.setErrorRenderer((node, error) => { - render(, node); - - return () => unmountComponentAtNode(node); - }), - [] - ); + useEffect(() => { + if (ref.current) { + ref.current.embeddable.catchError = (error) => { + return ; + }; + } + }, []); useEffect( () => void ref.current?.embeddable.store.dispatch(actions.output.setError(new Error(message))), [message] diff --git a/src/plugins/embeddable/public/__stories__/embeddable_root.stories.tsx b/src/plugins/embeddable/public/__stories__/embeddable_root.stories.tsx index e8ccc0edd66a2..2539185022f3f 100644 --- a/src/plugins/embeddable/public/__stories__/embeddable_root.stories.tsx +++ b/src/plugins/embeddable/public/__stories__/embeddable_root.stories.tsx @@ -65,7 +65,7 @@ Default.args = { loading: false, }; -export const DefaultWithError = Default as Meta; +export const DefaultWithError = Default.bind({}) as Meta; DefaultWithError.args = { ...Default.args, diff --git a/src/plugins/embeddable/public/__stories__/error_embeddable.stories.tsx b/src/plugins/embeddable/public/__stories__/error_embeddable.stories.tsx index ad65b2412c4c4..2d83d9b8ed702 100644 --- a/src/plugins/embeddable/public/__stories__/error_embeddable.stories.tsx +++ b/src/plugins/embeddable/public/__stories__/error_embeddable.stories.tsx @@ -6,14 +6,10 @@ * Side Public License, v 1. */ -import React, { useContext, useEffect, useMemo, useRef } from 'react'; -import { filter, ReplaySubject } from 'rxjs'; -import { ThemeContext } from '@emotion/react'; +import { useEffect, useMemo } from 'react'; import { Meta } from '@storybook/react'; -import { CoreTheme } from '@kbn/core-theme-browser'; import { ErrorEmbeddable } from '..'; -import { setTheme } from '../services'; export default { title: 'components/ErrorEmbeddable', @@ -26,32 +22,17 @@ export default { } as Meta; interface ErrorEmbeddableWrapperProps { - compact?: boolean; message: string; } -function ErrorEmbeddableWrapper({ compact, message }: ErrorEmbeddableWrapperProps) { +function ErrorEmbeddableWrapper({ message }: ErrorEmbeddableWrapperProps) { const embeddable = useMemo( - () => new ErrorEmbeddable(message, { id: `${Math.random()}` }, undefined, compact), - [compact, message] + () => new ErrorEmbeddable(message, { id: `${Math.random()}` }, undefined), + [message] ); - const root = useRef(null); - const theme$ = useMemo(() => new ReplaySubject(1), []); - const theme = useContext(ThemeContext) as CoreTheme; + useEffect(() => () => embeddable.destroy(), [embeddable]); - useEffect(() => setTheme({ theme$: theme$.pipe(filter(Boolean)) }), [theme$]); - useEffect(() => theme$.next(theme), [theme$, theme]); - useEffect(() => { - if (!root.current) { - return; - } - - embeddable.render(root.current); - - return () => embeddable.destroy(); - }, [embeddable]); - - return
    ; + return embeddable.render(); } export const Default = ErrorEmbeddableWrapper as Meta; @@ -59,9 +40,3 @@ export const Default = ErrorEmbeddableWrapper as Meta ( - -)) as Meta; - -DefaultCompact.args = { ...Default.args }; diff --git a/src/plugins/embeddable/public/__stories__/hello_world_embeddable.tsx b/src/plugins/embeddable/public/__stories__/hello_world_embeddable.tsx index 5cf2c5fdc46e8..d343425bced3e 100644 --- a/src/plugins/embeddable/public/__stories__/hello_world_embeddable.tsx +++ b/src/plugins/embeddable/public/__stories__/hello_world_embeddable.tsx @@ -7,10 +7,9 @@ */ import React from 'react'; -import { render } from 'react-dom'; import { connect, Provider } from 'react-redux'; import { EuiEmptyPrompt } from '@elastic/eui'; -import { Embeddable, IEmbeddable } from '..'; +import { Embeddable } from '..'; import { createStore, State } from '../store'; export class HelloWorldEmbeddable extends Embeddable { @@ -19,22 +18,15 @@ export class HelloWorldEmbeddable extends Embeddable { readonly type = 'hello-world'; - renderError: IEmbeddable['renderError']; - reload() {} - render(node: HTMLElement) { - const App = connect((state: State) => ({ body: state.input.title }))(EuiEmptyPrompt); + render() { + const HelloWorld = connect((state: State) => ({ body: state.input.title }))(EuiEmptyPrompt); - render( + return ( - - , - node + + ); } - - setErrorRenderer(renderer: IEmbeddable['renderError']) { - this.renderError = renderer; - } } diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx index de0bcf3f8654c..079a31089d707 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx @@ -43,7 +43,7 @@ class OutputTestEmbeddable extends Embeddable { reload() {} } -test('Embeddable calls input subscribers when changed', async (done) => { +test('Embeddable calls input subscribers when changed', (done) => { const hello = new ContactCardEmbeddable( { id: '123', firstName: 'Brienne', lastName: 'Tarth' }, { execAction: (() => null) as any } diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index 94025320ec86d..d1871ce2ffc98 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -14,7 +14,7 @@ import { debounceTime, distinctUntilChanged, map, skip } from 'rxjs/operators'; import { RenderCompleteDispatcher } from '@kbn/kibana-utils-plugin/public'; import { Adapters } from '../types'; import { IContainer } from '../containers'; -import { EmbeddableOutput, IEmbeddable } from './i_embeddable'; +import { EmbeddableError, EmbeddableOutput, IEmbeddable } from './i_embeddable'; import { EmbeddableInput, ViewMode } from '../../../common/types'; import { genericEmbeddableInputIsEqual, omitGenericEmbeddableInput } from './diff_embeddable_input'; @@ -23,8 +23,9 @@ function getPanelTitle(input: EmbeddableInput, output: EmbeddableOutput) { } export abstract class Embeddable< TEmbeddableInput extends EmbeddableInput = EmbeddableInput, - TEmbeddableOutput extends EmbeddableOutput = EmbeddableOutput -> implements IEmbeddable + TEmbeddableOutput extends EmbeddableOutput = EmbeddableOutput, + TNode = any +> implements IEmbeddable { static runtimeId: number = 0; @@ -33,6 +34,7 @@ export abstract class Embeddable< public readonly parent?: IContainer; public readonly isContainer: boolean = false; public readonly deferEmbeddableLoad: boolean = false; + public catchError?(error: EmbeddableError, domNode: HTMLElement | Element): TNode | (() => void); public abstract readonly type: string; public readonly id: string; @@ -209,14 +211,13 @@ export abstract class Embeddable< } } - public render(el: HTMLElement): void { + public render(el: HTMLElement): TNode | void { this.renderComplete.setEl(el); this.renderComplete.setTitle(this.output.title || ''); if (this.destroyed) { throw new Error('Embeddable has been destroyed'); } - return; } /** diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx index c5912427893a6..056c652e104f0 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { HelloWorldEmbeddable } from '../../tests/fixtures'; +import { HelloWorldEmbeddable, HelloWorldEmbeddableReact } from '../../tests/fixtures'; import { EmbeddableRoot } from './embeddable_root'; import { mount } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; @@ -25,6 +25,13 @@ test('EmbeddableRoot renders an embeddable', async () => { expect(findTestSubject(component, 'embedError').length).toBe(0); }); +test('EmbeddableRoot renders a React-based embeddable', async () => { + const embeddable = new HelloWorldEmbeddableReact({ id: 'hello' }); + const component = mount(); + + expect(component.find('[data-test-subj="helloWorldEmbeddable"]')).toHaveLength(1); +}); + test('EmbeddableRoot updates input', async () => { const embeddable = new HelloWorldEmbeddable({ id: 'hello' }); const component = mount(); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx index cab7fcbd54e1d..bfaefe09b5e6b 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_root.tsx @@ -6,19 +6,25 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { ReactNode } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; import { EuiText } from '@elastic/eui'; -import { EmbeddableInput, IEmbeddable } from './i_embeddable'; +import { isPromise } from '@kbn/std'; +import { MaybePromise } from '@kbn/utility-types'; +import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; interface Props { - embeddable?: IEmbeddable; + embeddable?: IEmbeddable>; loading?: boolean; error?: string; input?: EmbeddableInput; } -export class EmbeddableRoot extends React.Component { +interface State { + node?: ReactNode; +} + +export class EmbeddableRoot extends React.Component { private root?: React.RefObject; private alreadyMounted: boolean = false; @@ -26,20 +32,33 @@ export class EmbeddableRoot extends React.Component { super(props); this.root = React.createRef(); + this.state = {}; } + private updateNode = (node: MaybePromise) => { + if (isPromise(node)) { + node.then(this.updateNode); + + return; + } + + this.setState({ node }); + }; + public componentDidMount() { - if (this.root && this.root.current && this.props.embeddable) { - this.alreadyMounted = true; - this.props.embeddable.render(this.root.current); + if (!this.root?.current || !this.props.embeddable) { + return; } + + this.alreadyMounted = true; + this.updateNode(this.props.embeddable.render(this.root.current) ?? undefined); } public componentDidUpdate(prevProps?: Props) { let justRendered = false; - if (this.root && this.root.current && this.props.embeddable && !this.alreadyMounted) { + if (this.root?.current && this.props.embeddable && !this.alreadyMounted) { this.alreadyMounted = true; - this.props.embeddable.render(this.root.current); + this.updateNode(this.props.embeddable.render(this.root.current) ?? undefined); justRendered = true; } @@ -56,20 +75,21 @@ export class EmbeddableRoot extends React.Component { } } - public shouldComponentUpdate(newProps: Props) { + public shouldComponentUpdate({ embeddable, error, input, loading }: Props, { node }: State) { return Boolean( - newProps.error !== this.props.error || - newProps.loading !== this.props.loading || - newProps.embeddable !== this.props.embeddable || - (this.root && this.root.current && newProps.embeddable && !this.alreadyMounted) || - newProps.input !== this.props.input + error !== this.props.error || + loading !== this.props.loading || + embeddable !== this.props.embeddable || + (this.root && this.root.current && embeddable && !this.alreadyMounted) || + input !== this.props.input || + node !== this.state.node ); } public render() { return ( -
    +
    {this.state.node}
    {this.props.loading && } {this.props.error && {this.props.error}} diff --git a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx index 8f17a3bf84198..d932018c3f4fe 100644 --- a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx @@ -20,47 +20,6 @@ test('ErrorEmbeddable renders an embeddable', async () => { expect(getByText(/some error occurred/i)).toBeVisible(); }); -test('ErrorEmbeddable renders in compact mode', async () => { - const embeddable = new ErrorEmbeddable( - 'some error occurred', - { id: '123', title: 'Error' }, - undefined, - true - ); - const component = render(); - - expect(component.baseElement).toMatchInlineSnapshot(` - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    - - `); -}); - test('ErrorEmbeddable renders an embeddable with markdown message', async () => { const error = '[some link](http://localhost:5601/takeMeThere)'; const embeddable = new ErrorEmbeddable(error, { id: '123', title: 'Error' }); diff --git a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx index 55946aad5da02..8dff4ecee8976 100644 --- a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx @@ -6,16 +6,12 @@ * Side Public License, v 1. */ -import { EuiText, EuiIcon, EuiPopover, EuiLink, EuiEmptyPrompt } from '@elastic/eui'; -import React, { useState } from 'react'; -import ReactDOM from 'react-dom'; -import { KibanaThemeProvider, Markdown } from '@kbn/kibana-react-plugin/public'; -import { i18n } from '@kbn/i18n'; +import { EuiEmptyPrompt } from '@elastic/eui'; +import React, { ReactNode } from 'react'; +import { Markdown } from '@kbn/kibana-react-plugin/public'; import { Embeddable } from './embeddable'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; import { IContainer } from '../containers'; -import { getTheme } from '../../services'; -import './error_embedabble.scss'; export const ERROR_EMBEDDABLE_TYPE = 'error'; @@ -25,91 +21,32 @@ export function isErrorEmbeddable( return Boolean(embeddable.fatalError || (embeddable as ErrorEmbeddable).error !== undefined); } -export class ErrorEmbeddable extends Embeddable { +export class ErrorEmbeddable extends Embeddable { public readonly type = ERROR_EMBEDDABLE_TYPE; public error: Error | string; - private dom?: HTMLElement; - constructor( - error: Error | string, - input: EmbeddableInput, - parent?: IContainer, - private compact: boolean = false - ) { + constructor(error: Error | string, input: EmbeddableInput, parent?: IContainer) { super(input, {}, parent); this.error = error; } public reload() {} - public render(dom: HTMLElement) { + public render() { const title = typeof this.error === 'string' ? this.error : this.error.message; - this.dom = dom; - let theme; - try { - theme = getTheme(); - } catch (err) { - theme = {}; - } - const errorMarkdown = ( + const body = ( ); - const node = this.compact ? ( - {errorMarkdown} - ) : ( + return (
    ); - const content = - theme && theme.theme$ ? ( - {node} - ) : ( - node - ); - - ReactDOM.render(content, dom); - } - - public destroy() { - if (this.dom) { - ReactDOM.unmountComponentAtNode(this.dom); - } } } - -const CompactEmbeddableError = ({ children }: { children?: React.ReactNode }) => { - const [isPopoverOpen, setPopoverOpen] = useState(false); - - const popoverButton = ( - - setPopoverOpen((open) => !open)} - > - - {i18n.translate('embeddableApi.panel.errorEmbeddable.message', { - defaultMessage: 'An error has occurred. Read more', - })} - - - ); - - return ( - setPopoverOpen(false)} - > - {children} - - ); -}; diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 1c9bdebcefc9b..1d3cc7980ad62 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -33,7 +33,8 @@ export interface EmbeddableOutput { export interface IEmbeddable< I extends EmbeddableInput = EmbeddableInput, - O extends EmbeddableOutput = EmbeddableOutput + O extends EmbeddableOutput = EmbeddableOutput, + N = any > { /** * Is this embeddable an instance of a Container class, can it contain @@ -172,15 +173,17 @@ export interface IEmbeddable< /** * Renders the embeddable at the given node. * @param domNode + * @returns A React node to mount or void in the case when rendering is done without React. */ - render(domNode: HTMLElement | Element): void; + render(domNode: HTMLElement | Element): N | void; /** * Renders a custom embeddable error at the given node. + * @param error * @param domNode - * @returns A callback that will be called on error destroy. + * @returns A React node or callback that will be called on error destroy. */ - renderError?(domNode: HTMLElement | Element, error: ErrorLike): () => void; + catchError?(error: EmbeddableError, domNode: HTMLElement | Element): N | (() => void); /** * Reload the embeddable so output and rendering is up to date. Especially relevant diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index a79f19cb4225c..d482354ca509e 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -17,17 +17,17 @@ import { Action, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import { Trigger, ViewMode } from '../types'; import { isErrorEmbeddable } from '../embeddables'; import { EmbeddablePanel } from './embeddable_panel'; -import { createEditModeAction } from '../test_samples/actions'; -import { - ContactCardEmbeddableFactory, - CONTACT_CARD_EMBEDDABLE, -} from '../test_samples/embeddables/contact_card/contact_card_embeddable_factory'; -import { HelloWorldContainer } from '../test_samples/embeddables/hello_world_container'; import { + createEditModeAction, ContactCardEmbeddable, ContactCardEmbeddableInput, ContactCardEmbeddableOutput, -} from '../test_samples/embeddables/contact_card/contact_card_embeddable'; + ContactCardEmbeddableFactory, + ContactCardEmbeddableReactFactory, + CONTACT_CARD_EMBEDDABLE, + CONTACT_CARD_EMBEDDABLE_REACT, + HelloWorldContainer, +} from '../test_samples'; import { inspectorPluginMock } from '@kbn/inspector-plugin/public/mocks'; import { EuiBadge } from '@elastic/eui'; import { embeddablePluginMock } from '../../mocks'; @@ -43,16 +43,21 @@ const trigger: Trigger = { id: CONTEXT_MENU_TRIGGER, }; const embeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); +const embeddableReactFactory = new ContactCardEmbeddableReactFactory( + (() => null) as any, + {} as any +); const applicationMock = applicationServiceMock.createStartContract(); const theme = themeServiceMock.createStartContract(); actionRegistry.set(editModeAction.id, editModeAction); triggerRegistry.set(trigger.id, trigger); setup.registerEmbeddableFactory(embeddableFactory.type, embeddableFactory); +setup.registerEmbeddableFactory(embeddableReactFactory.type, embeddableReactFactory); const start = doStart(); const getEmbeddableFactory = start.getEmbeddableFactory; -test('HelloWorldContainer initializes embeddables', async (done) => { +test('HelloWorldContainer initializes embeddables', (done) => { const container = new HelloWorldContainer( { id: '123', @@ -198,7 +203,7 @@ describe('HelloWorldContainer in error state', () => { ); - jest.spyOn(embeddable, 'renderError'); + jest.spyOn(embeddable, 'catchError'); }); test('renders a custom error', () => { @@ -207,9 +212,9 @@ describe('HelloWorldContainer in error state', () => { const embeddableError = findTestSubject(component, 'embeddableError'); - expect(embeddable.renderError).toHaveBeenCalledWith( - expect.any(HTMLElement), - new Error('something') + expect(embeddable.catchError).toHaveBeenCalledWith( + new Error('something'), + expect.any(HTMLElement) ); expect(embeddableError).toHaveProperty('length', 1); expect(embeddableError.text()).toBe('something'); @@ -222,21 +227,21 @@ describe('HelloWorldContainer in error state', () => { const embeddableError = findTestSubject(component, 'embeddableError'); - expect(embeddable.renderError).toHaveBeenCalledWith( - expect.any(HTMLElement), - new Error('something') + expect(embeddable.catchError).toHaveBeenCalledWith( + new Error('something'), + expect.any(HTMLElement) ); expect(embeddableError).toHaveProperty('length', 1); expect(embeddableError.text()).toBe('something'); }); test('destroys previous error', () => { - const { renderError } = embeddable as Required; - let destroyError: jest.MockedFunction>; + const { catchError } = embeddable as Required; + let destroyError: jest.MockedFunction>; - (embeddable.renderError as jest.MockedFunction).mockImplementationOnce( + (embeddable.catchError as jest.MockedFunction).mockImplementationOnce( (...args) => { - destroyError = jest.fn(renderError(...args)); + destroyError = jest.fn(catchError(...args)); return destroyError; } @@ -254,7 +259,7 @@ describe('HelloWorldContainer in error state', () => { }); test('renders a default error', async () => { - embeddable.renderError = undefined; + embeddable.catchError = undefined; embeddable.triggerError(new Error('something')); component.update(); @@ -263,6 +268,17 @@ describe('HelloWorldContainer in error state', () => { expect(embeddableError).toHaveProperty('length', 1); expect(embeddableError.children.length).toBeGreaterThan(0); }); + + test('renders a React node', () => { + (embeddable.catchError as jest.Mock).mockReturnValueOnce(
    Something
    ); + embeddable.triggerError(new Error('something')); + component.update(); + + const embeddableError = findTestSubject(component, 'embeddableError'); + + expect(embeddableError).toHaveProperty('length', 1); + expect(embeddableError.text()).toBe('Something'); + }); }); const renderInEditModeAndOpenContextMenu = async ( @@ -735,3 +751,37 @@ test('Should work in minimal way rendering only the inspector action', async () const action = findTestSubject(component, `embeddablePanelAction-ACTION_CUSTOMIZE_PANEL`); expect(action.length).toBe(0); }); + +test('Renders an embeddable returning a React node', async () => { + const container = new HelloWorldContainer( + { id: '123', panels: {}, viewMode: ViewMode.VIEW, hidePanelTitles: false }, + { getEmbeddableFactory } as any + ); + + const embeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE_REACT, { + firstName: 'Bran', + lastName: 'Stark', + }); + + const component = mount( + + Promise.resolve([])} + getAllEmbeddableFactories={start.getEmbeddableFactories} + getEmbeddableFactory={start.getEmbeddableFactory} + notifications={{} as any} + overlays={{} as any} + application={applicationMock} + SavedObjectFinder={() => null} + theme={theme} + /> + + ); + + expect(component.find('.embPanel__titleText').text()).toBe('Hello Bran Stark'); +}); diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 52b70f3b53406..f5b072a591225 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -8,12 +8,14 @@ import { EuiContextMenuPanelDescriptor, EuiPanel, htmlIdGenerator } from '@elastic/eui'; import classNames from 'classnames'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { Subscription } from 'rxjs'; import deepEqual from 'fast-deep-equal'; import { CoreStart, OverlayStart, ThemeServiceStart } from '@kbn/core/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { isPromise } from '@kbn/std'; import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import { MaybePromise } from '@kbn/utility-types'; import { buildContextMenuForActions, UiActionsService, Action } from '../ui_actions'; import { Start as InspectorStartContract } from '../inspector'; @@ -66,7 +68,7 @@ export interface EmbeddableContainerContext { } interface Props { - embeddable: IEmbeddable; + embeddable: IEmbeddable>; /** * Ordinal number of the embeddable in the container, used as a @@ -105,6 +107,7 @@ interface State { loading?: boolean; error?: EmbeddableError; destroyError?(): void; + node?: ReactNode; } interface InspectorPanelAction { @@ -304,27 +307,37 @@ export class EmbeddablePanel extends React.Component { error={this.state.error} /> )} -
    +
    + {this.state.node} +
    ); } public componentDidMount() { - if (this.embeddableRoot.current) { - this.subscription.add( - this.props.embeddable.getOutput$().subscribe( - (output: EmbeddableOutput) => { - this.setState({ - error: output.error, - loading: output.loading, - }); - }, - (error) => { - this.setState({ error }); - } - ) - ); - this.props.embeddable.render(this.embeddableRoot.current); + if (!this.embeddableRoot.current) { + return; + } + + this.subscription.add( + this.props.embeddable.getOutput$().subscribe( + (output: EmbeddableOutput) => { + this.setState({ + error: output.error, + loading: output.loading, + }); + }, + (error) => { + this.setState({ error }); + } + ) + ); + + const node = this.props.embeddable.render(this.embeddableRoot.current) ?? undefined; + if (isPromise(node)) { + node.then((resolved) => this.setState({ node: resolved })); + } else { + this.setState({ node }); } } diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel_error.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel_error.tsx index 46e26fd1448bb..69af8e7220e62 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel_error.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel_error.tsx @@ -6,17 +6,20 @@ * Side Public License, v 1. */ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { isFunction } from 'lodash'; +import React, { ReactNode, useEffect, useMemo, useRef, useState } from 'react'; import { EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { isPromise } from '@kbn/std'; +import type { MaybePromise } from '@kbn/utility-types'; import { ErrorLike } from '@kbn/expressions-plugin/common'; import { distinctUntilChanged, merge, of, switchMap } from 'rxjs'; import { EditPanelAction } from '../actions'; -import { ErrorEmbeddable, IEmbeddable } from '../embeddables'; +import { EmbeddableInput, EmbeddableOutput, ErrorEmbeddable, IEmbeddable } from '../embeddables'; interface EmbeddablePanelErrorProps { editPanelAction?: EditPanelAction; - embeddable: IEmbeddable; + embeddable: IEmbeddable>; error: ErrorLike; } @@ -26,6 +29,7 @@ export function EmbeddablePanelError({ error, }: EmbeddablePanelErrorProps) { const [isEditable, setEditable] = useState(false); + const [node, setNode] = useState(); const ref = useRef(null); const handleErrorClick = useMemo( () => (isEditable ? () => editPanelAction?.execute({ embeddable }) : undefined), @@ -63,14 +67,22 @@ export function EmbeddablePanelError({ return; } - if (embeddable.renderError) { - return embeddable.renderError(ref.current, error); - } + if (!embeddable.catchError) { + const errorEmbeddable = new ErrorEmbeddable(error, { id: embeddable.id }); + setNode(errorEmbeddable.render()); - const errorEmbeddable = new ErrorEmbeddable(error, { id: embeddable.id }); - errorEmbeddable.render(ref.current); + return () => errorEmbeddable.destroy(); + } - return () => errorEmbeddable.destroy(); + const renderedNode = embeddable.catchError(error, ref.current); + if (isFunction(renderedNode)) { + return renderedNode; + } + if (isPromise(renderedNode)) { + renderedNode.then(setNode); + } else { + setNode(renderedNode); + } }, [embeddable, error]); return ( @@ -84,6 +96,8 @@ export function EmbeddablePanelError({ role={isEditable ? 'button' : undefined} aria-label={isEditable ? ariaLabel : undefined} onClick={handleErrorClick} - /> + > + {node} + ); } diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts index 983f9ceedf369..58f15c326be77 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts @@ -53,7 +53,7 @@ beforeEach(async () => { } }); -test('Updates the embeddable title when given', async (done) => { +test('Updates the embeddable title when given', async () => { const getUserData = () => Promise.resolve({ title: 'What is up?' }); const customizePanelAction = new CustomizePanelTitleAction(getUserData); expect(embeddable.getInput().title).toBeUndefined(); @@ -66,11 +66,10 @@ test('Updates the embeddable title when given', async (done) => { // Recreating the container should preserve the custom title. const containerClone = createHelloWorldContainer(container.getInput()); // Need to wait for the container to tell us the embeddable has been loaded. - const subscription = containerClone.getOutput$().subscribe(() => { + const subscription = await containerClone.getOutput$().subscribe(() => { if (containerClone.getOutput().embeddableLoaded[embeddable.id]) { expect(embeddable.getInput().title).toBe('What is up?'); subscription.unsubscribe(); - done(); } }); }); diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx index 7d9a929299f35..0287b9d115827 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable.tsx @@ -47,7 +47,7 @@ export class ContactCardEmbeddable extends Embeddable< constructor( initialInput: ContactCardEmbeddableInput, - private readonly options: ContactCardEmbeddableOptions, + protected readonly options: ContactCardEmbeddableOptions, parent?: Container ) { super( @@ -77,7 +77,7 @@ export class ContactCardEmbeddable extends Embeddable< ); } - public renderError?(node: HTMLElement, error: ErrorLike) { + public catchError?(error: ErrorLike, node: HTMLElement) { ReactDom.render(
    {error.message}
    , node); return () => ReactDom.unmountComponentAtNode(node); diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx index 282f6a8c627c2..317e0d5e741c8 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_factory.tsx @@ -25,7 +25,7 @@ export class ContactCardEmbeddableFactory public readonly type = CONTACT_CARD_EMBEDDABLE; constructor( - private readonly execTrigger: UiActionsStart['executeTriggerActions'], + protected readonly execTrigger: UiActionsStart['executeTriggerActions'], private readonly overlays: CoreStart['overlays'] ) {} diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_react.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_react.tsx new file mode 100644 index 0000000000000..d42ba42a0cfb3 --- /dev/null +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_react.tsx @@ -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 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 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { ContactCardEmbeddableComponent } from './contact_card'; +import { ContactCardEmbeddable } from './contact_card_embeddable'; + +export class ContactCardEmbeddableReact extends ContactCardEmbeddable { + public render() { + return ( + + ); + } +} diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_react_factory.ts b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_react_factory.ts new file mode 100644 index 0000000000000..7378dc24ea5f8 --- /dev/null +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/contact_card_embeddable_react_factory.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 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 or the Server + * Side Public License, v 1. + */ + +import { Container } from '../../../containers'; +import { ContactCardEmbeddableInput } from './contact_card_embeddable'; +import { ContactCardEmbeddableFactory } from './contact_card_embeddable_factory'; +import { ContactCardEmbeddableReact } from './contact_card_embeddable_react'; + +export const CONTACT_CARD_EMBEDDABLE_REACT = 'CONTACT_CARD_EMBEDDABLE_REACT'; + +export class ContactCardEmbeddableReactFactory extends ContactCardEmbeddableFactory { + public readonly type = CONTACT_CARD_EMBEDDABLE_REACT as ContactCardEmbeddableFactory['type']; + + public create = async (initialInput: ContactCardEmbeddableInput, parent?: Container) => { + return new ContactCardEmbeddableReact( + initialInput, + { + execAction: this.execTrigger, + }, + parent + ); + }; +} diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/index.ts b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/index.ts index 526256d375963..fc63fcacbab79 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/index.ts +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/contact_card/index.ts @@ -11,5 +11,7 @@ export * from './contact_card_embeddable'; export * from './contact_card_embeddable_factory'; export * from './contact_card_exportable_embeddable'; export * from './contact_card_exportable_embeddable_factory'; +export * from './contact_card_embeddable_react'; +export * from './contact_card_embeddable_react_factory'; export * from './contact_card_initializer'; export * from './slow_contact_card_embeddable_factory'; diff --git a/src/plugins/embeddable/public/tests/container.test.ts b/src/plugins/embeddable/public/tests/container.test.ts index 8b487d9f02f78..c6088e95069cb 100644 --- a/src/plugins/embeddable/public/tests/container.test.ts +++ b/src/plugins/embeddable/public/tests/container.test.ts @@ -128,7 +128,7 @@ describe('container initialization', () => { expect(embeddable.id).toBe('123'); }; - it('initializes embeddables', async (done) => { + it('initializes embeddables', async () => { const { container } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels, @@ -137,10 +137,9 @@ describe('container initialization', () => { expectEmbeddableLoaded(container, '123'); expectEmbeddableLoaded(container, '456'); expectEmbeddableLoaded(container, '789'); - done(); }); - it('initializes embeddables once and only once with multiple input updates', async (done) => { + it('initializes embeddables once and only once with multiple input updates', async () => { const { container, contactCardCreateSpy } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels, @@ -148,10 +147,9 @@ describe('container initialization', () => { container.updateInput({ lastReloadRequestTime: 1 }); container.updateInput({ lastReloadRequestTime: 2 }); expect(contactCardCreateSpy).toHaveBeenCalledTimes(4); - done(); }); - it('initializes embeddables in order', async (done) => { + it('initializes embeddables in order', async () => { const childIdInitializeOrder = ['456', '123', '789']; const { contactCardCreateSpy } = await createHelloWorldContainerAndEmbeddable( { @@ -170,10 +168,9 @@ describe('container initialization', () => { expect.anything() // parent passed into create method ); } - done(); }); - it('initializes embeddables in order with partial order arg', async (done) => { + it('initializes embeddables in order with partial order arg', async () => { const childIdInitializeOrder = ['789', 'idontexist']; const { contactCardCreateSpy } = await createHelloWorldContainerAndEmbeddable( { @@ -193,10 +190,9 @@ describe('container initialization', () => { expect.anything() // parent passed into create method ); } - done(); }); - it('initializes embeddables in order, awaiting each', async (done) => { + it('initializes embeddables in order, awaiting each', async () => { const childIdInitializeOrder = ['456', '123', '789']; const { container, contactCardCreateSpy } = await createHelloWorldContainerAndEmbeddable( { @@ -220,7 +216,6 @@ describe('container initialization', () => { ); expect(untilEmbeddableLoadedMock).toHaveBeenCalledWith(orderedId); } - done(); }); }); @@ -244,7 +239,7 @@ test('Container.addNewEmbeddable', async () => { expect(embeddableInContainer.id).toBe(embeddable.id); }); -test('Container.removeEmbeddable removes and cleans up', async (done) => { +test('Container.removeEmbeddable removes and cleans up', async () => { const { start, testPanel } = await createHelloWorldContainerAndEmbeddable(); const container = new HelloWorldContainer( @@ -288,12 +283,10 @@ test('Container.removeEmbeddable removes and cleans up', async (done) => { expect(container.getInput().panels[embeddable.id]).toBeUndefined(); if (isErrorEmbeddable(embeddable)) { expect(false).toBe(true); - done(); } expect(() => embeddable.updateInput({ nameTitle: 'Sir' })).toThrowError(); expect(container.getOutput().embeddableLoaded[embeddable.id]).toBeUndefined(); - done(); }); container.removeEmbeddable(embeddable.id); @@ -403,7 +396,7 @@ test('Container view mode change propagates to children', async () => { expect(embeddable.getInput().viewMode).toBe(ViewMode.EDIT); }); -test(`Container updates its state when a child's input is updated`, async (done) => { +test(`Container updates its state when a child's input is updated`, async () => { const { container, embeddable, start, coreStart, uiActions } = await createHelloWorldContainerAndEmbeddable( { id: 'hello', panels: {}, viewMode: ViewMode.VIEW }, @@ -450,7 +443,6 @@ test(`Container updates its state when a child's input is updated`, async (done) childClone.getInput().nameTitle === 'Dr.' ) { cloneSubscription.unsubscribe(); - done(); } }); } @@ -487,7 +479,7 @@ test(`Derived container state passed to children`, async () => { subscription.unsubscribe(); }); -test(`Can subscribe to children embeddable updates`, async (done) => { +test(`Can subscribe to children embeddable updates`, async () => { const { embeddable } = await createHelloWorldContainerAndEmbeddable( { id: 'hello container', @@ -504,13 +496,12 @@ test(`Can subscribe to children embeddable updates`, async (done) => { const subscription = embeddable.getInput$().subscribe((input: ContactCardEmbeddableInput) => { if (input.nameTitle === 'Dr.') { subscription.unsubscribe(); - done(); } }); embeddable.updateInput({ nameTitle: 'Dr.' }); }); -test('Test nested reactions', async (done) => { +test('Test nested reactions', async () => { const { container, embeddable } = await createHelloWorldContainerAndEmbeddable( { id: 'hello', panels: {}, viewMode: ViewMode.VIEW }, { @@ -533,7 +524,6 @@ test('Test nested reactions', async (done) => { ) { containerSubscription.unsubscribe(); embeddableSubscription.unsubscribe(); - done(); } }); @@ -578,7 +568,7 @@ test('Explicit embeddable input mapped to undefined will default to inherited', ]); }); -test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async (done) => { +test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async () => { const { container } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels: {} }); const embeddable = await container.addNewEmbeddable< @@ -601,7 +591,6 @@ test('Explicit embeddable input mapped to undefined with no inherited value will .subscribe(() => { if (embeddable.getInput().filters === undefined) { subscription.unsubscribe(); - done(); } }); @@ -666,7 +655,7 @@ test('Panel added to input state', async () => { expect(container.getOutput().embeddableLoaded[embeddable2.id]).toBe(true); }); -test('Container changes made directly after adding a new embeddable are propagated', async (done) => { +test('Container changes made directly after adding a new embeddable are propagated', async () => { const coreSetup = coreMock.createSetup(); const coreStart = coreMock.createStart(); const { setup, doStart, uiActions } = testPlugin(coreSetup, coreStart); @@ -710,7 +699,6 @@ test('Container changes made directly after adding a new embeddable are propagat const embeddable = container.getChild(embeddableId); if (embeddable.getInput().viewMode === ViewMode.VIEW) { subscription.unsubscribe(); - done(); } } } @@ -724,7 +712,7 @@ test('Container changes made directly after adding a new embeddable are propagat container.updateInput({ viewMode: ViewMode.VIEW }); }); -test('container stores ErrorEmbeddables when a factory for a child cannot be found (so the panel can be removed)', async (done) => { +test('container stores ErrorEmbeddables when a factory for a child cannot be found (so the panel can be removed)', async () => { const { container } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels: { @@ -740,12 +728,11 @@ test('container stores ErrorEmbeddables when a factory for a child cannot be fou if (container.getOutput().embeddableLoaded['123']) { const child = container.getChild('123'); expect(child.type).toBe(ERROR_EMBEDDABLE_TYPE); - done(); } }); }); -test('container stores ErrorEmbeddables when a saved object cannot be found', async (done) => { +test('container stores ErrorEmbeddables when a saved object cannot be found', async () => { const { container } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels: { @@ -761,12 +748,11 @@ test('container stores ErrorEmbeddables when a saved object cannot be found', as if (container.getOutput().embeddableLoaded['123']) { const child = container.getChild('123'); expect(child.type).toBe(ERROR_EMBEDDABLE_TYPE); - done(); } }); }); -test('ErrorEmbeddables get updated when parent does', async (done) => { +test('ErrorEmbeddables get updated when parent does', async () => { const { container } = await createHelloWorldContainerAndEmbeddable({ id: 'hello', panels: { @@ -787,7 +773,6 @@ test('ErrorEmbeddables get updated when parent does', async (done) => { container.updateInput({ viewMode: ViewMode.VIEW }); expect(embeddable.getInput().viewMode).toBe(ViewMode.VIEW); - done(); } }); }); @@ -831,7 +816,7 @@ test('untilEmbeddableLoaded() throws an error if there is no such child panel in expect((error as Error).message).toMatchInlineSnapshot(`"Panel not found"`); }); -test('untilEmbeddableLoaded() resolves if child is loaded in the container', async (done) => { +test('untilEmbeddableLoaded() resolves if child is loaded in the container', async () => { const { setup, doStart, coreStart, uiActions } = testPlugin( coreMock.createSetup(), coreMock.createStart() @@ -866,10 +851,9 @@ test('untilEmbeddableLoaded() resolves if child is loaded in the container', asy const child = await container.untilEmbeddableLoaded('123'); expect(child).toBeDefined(); expect(child.type).toBe(HELLO_WORLD_EMBEDDABLE); - done(); }); -test('untilEmbeddableLoaded resolves with undefined if child is subsequently removed', async (done) => { +test('untilEmbeddableLoaded resolves with undefined if child is subsequently removed', async () => { const { doStart, setup, coreStart, uiActions } = testPlugin( coreMock.createSetup(), coreMock.createStart() @@ -907,13 +891,12 @@ test('untilEmbeddableLoaded resolves with undefined if child is subsequently rem container.untilEmbeddableLoaded('123').then((embed) => { expect(embed).toBeUndefined(); - done(); }); container.updateInput({ panels: {} }); }); -test('adding a panel then subsequently removing it before its loaded removes the panel', async (done) => { +test('adding a panel then subsequently removing it before its loaded removes the panel', (done) => { const { doStart, coreStart, uiActions, setup } = testPlugin( coreMock.createSetup(), coreMock.createStart() diff --git a/src/plugins/embeddable/public/tests/explicit_input.test.ts b/src/plugins/embeddable/public/tests/explicit_input.test.ts index d9a7b72ada15a..4ed4c12a80390 100644 --- a/src/plugins/embeddable/public/tests/explicit_input.test.ts +++ b/src/plugins/embeddable/public/tests/explicit_input.test.ts @@ -68,7 +68,7 @@ test('Explicit embeddable input mapped to undefined will default to inherited', ]); }); -test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async (done) => { +test('Explicit embeddable input mapped to undefined with no inherited value will get passed to embeddable', async () => { const testPanel = createEmbeddablePanelMock({ getActions: uiActions.getTriggerCompatibleActions, getEmbeddableFactory: start.getEmbeddableFactory, @@ -95,13 +95,12 @@ test('Explicit embeddable input mapped to undefined with no inherited value will expect(container.getInputForChild(embeddable.id).filters).toEqual([]); - const subscription = embeddable + const subscription = await embeddable .getInput$() .pipe(skip(1)) .subscribe(() => { if (embeddable.getInput().filters === undefined) { subscription.unsubscribe(); - done(); } }); diff --git a/src/plugins/embeddable/public/tests/fixtures/hello_world_embeddable_react.tsx b/src/plugins/embeddable/public/tests/fixtures/hello_world_embeddable_react.tsx new file mode 100644 index 0000000000000..aa9ac3175fd5e --- /dev/null +++ b/src/plugins/embeddable/public/tests/fixtures/hello_world_embeddable_react.tsx @@ -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 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 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { HelloWorldEmbeddable } from './hello_world_embeddable'; + +export class HelloWorldEmbeddableReact extends HelloWorldEmbeddable { + public render() { + return
    HELLO WORLD!
    ; + } +} diff --git a/src/plugins/embeddable/public/tests/fixtures/index.ts b/src/plugins/embeddable/public/tests/fixtures/index.ts index a155f65d47858..ea9533a359ca7 100644 --- a/src/plugins/embeddable/public/tests/fixtures/index.ts +++ b/src/plugins/embeddable/public/tests/fixtures/index.ts @@ -8,3 +8,4 @@ export * from './hello_world_embeddable'; export * from './hello_world_embeddable_factory'; +export * from './hello_world_embeddable_react'; diff --git a/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx b/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx index db694a29b793e..b75b645846eed 100644 --- a/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx +++ b/src/plugins/es_ui_shared/public/request/use_request.test.helpers.tsx @@ -49,7 +49,7 @@ const errorWithBodyResponse = { body: errorValue }; export const createUseRequestHelpers = (): UseRequestHelpers => { // The behavior we're testing involves state changes over time, so we need finer control over // timing. - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const flushPromiseJobQueue = async () => { // See https://stackoverflow.com/questions/52177631/jest-timer-and-promise-dont-work-well-settimeout-and-async-function diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx index 8e9bdd35ffb7e..7a2d61c55d6c4 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx @@ -18,7 +18,7 @@ import { UseArray } from './use_array'; describe('', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx index 36fd16209f5d4..6ea28fc601869 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx @@ -19,7 +19,7 @@ import { UseField } from './use_field'; describe('', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx index 94c36613c44ba..09b0c898fc0dc 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx @@ -15,7 +15,7 @@ import { UseMultiFields } from './use_multi_fields'; describe('', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx index 71aafa6376884..4a3b5c99b978c 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx @@ -17,7 +17,7 @@ import { FieldHook, FieldValidateResponse, VALIDATION_TYPES, FieldConfig } from describe('useField() hook', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx index 9f57432b5d6a1..740dbf5ee5e75 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx @@ -40,7 +40,7 @@ const onFormHook = (_form: FormHook) => { describe('useForm() hook', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/expressions/common/execution/execution.abortion.test.ts b/src/plugins/expressions/common/execution/execution.abortion.test.ts index 1664dd86870a8..71dca4f60c721 100644 --- a/src/plugins/expressions/common/execution/execution.abortion.test.ts +++ b/src/plugins/expressions/common/execution/execution.abortion.test.ts @@ -13,7 +13,7 @@ import { parseExpression } from '../ast'; import { createUnitTestExecutor } from '../test_helpers'; import { ExpressionFunctionDefinition } from '../expression_functions'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); beforeEach(() => { jest.clearAllTimers(); @@ -82,7 +82,7 @@ describe('Execution abortion tests', () => { expect(result).toBe(null); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); test('nested expressions are aborted when parent aborted', async () => { @@ -151,6 +151,6 @@ describe('Execution abortion tests', () => { expect(aborted).toHaveBeenCalledTimes(1); expect(completed).toHaveBeenCalledTimes(0); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); }); diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index a5e03084a6977..3575552401914 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -14,6 +14,7 @@ import { parseExpression, ExpressionAstExpression } from '../ast'; import { createUnitTestExecutor } from '../test_helpers'; import { ExpressionFunctionDefinition } from '..'; import { ExecutionContract } from './execution_contract'; +import { ExpressionValueBoxed } from '../expression_types'; beforeAll(() => { if (typeof performance === 'undefined') { @@ -386,7 +387,7 @@ describe('Execution', () => { }); test('result is undefined until execution completes', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const execution = createExecution('sleep 10'); expect(execution.state.get().result).toBe(undefined); execution.start(null).subscribe(jest.fn()); @@ -560,7 +561,7 @@ describe('Execution', () => { }); test('execution state is "pending" while execution is in progress', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const execution = createExecution('sleep 20'); execution.start(null); jest.advanceTimersByTime(5); @@ -744,6 +745,79 @@ describe('Execution', () => { }); }); }); + + test('continues execution when error state is gone', async () => { + testScheduler.run(({ cold, expectObservable, flush }) => { + const a = 1; + const b = 2; + const c = 3; + const d = 4; + const observable$ = cold('abcd|', { a, b, c, d }); + const flakyFn = jest + .fn() + .mockImplementationOnce((value) => value) + .mockImplementationOnce(() => { + throw new Error('Some error.'); + }) + .mockReturnValueOnce({ type: 'something' }) + .mockImplementationOnce((value) => value); + const spyFn = jest.fn((input, { arg }) => arg); + + const executor = createUnitTestExecutor(); + executor.registerFunction({ + name: 'observable', + args: {}, + help: '', + fn: () => observable$, + }); + executor.registerFunction({ + name: 'flaky', + args: {}, + help: '', + fn: (value) => flakyFn(value), + }); + executor.registerFunction({ + name: 'spy', + args: { + arg: { + help: '', + types: ['number'], + }, + }, + help: '', + fn: (input, args) => spyFn(input, args), + }); + + const result = executor.run('spy arg={observable | flaky}', null, {}); + + expectObservable(result).toBe('abcd|', { + a: { partial: true, result: a }, + b: { + partial: true, + result: { + type: 'error', + error: expect.objectContaining({ message: '[spy] > [flaky] > Some error.' }), + }, + }, + c: { + partial: true, + result: { + type: 'error', + error: expect.objectContaining({ + message: `[spy] > Can not cast 'something' to any of 'number'`, + }), + }, + }, + d: { partial: false, result: d }, + }); + + flush(); + + expect(spyFn).toHaveBeenCalledTimes(2); + expect(spyFn).toHaveBeenNthCalledWith(1, null, { arg: a }); + expect(spyFn).toHaveBeenNthCalledWith(2, null, { arg: d }); + }); + }); }); describe('when arguments are missing', () => { @@ -847,6 +921,38 @@ describe('Execution', () => { }); }); + describe('when arguments are incorrect', () => { + it('when required argument is missing and has not alias, returns error', async () => { + const incorrectArg: ExpressionFunctionDefinition< + 'incorrectArg', + unknown, + { arg: ExpressionValueBoxed<'something'> }, + unknown + > = { + name: 'incorrectArg', + args: { + arg: { + help: '', + required: true, + types: ['something'], + }, + }, + help: '', + fn: jest.fn(), + }; + const executor = createUnitTestExecutor(); + executor.registerFunction(incorrectArg); + const { result } = await lastValueFrom(executor.run('incorrectArg arg="string"', null, {})); + + expect(result).toMatchObject({ + type: 'error', + error: { + message: `[incorrectArg] > Can not cast 'string' to any of 'something'`, + }, + }); + }); + }); + describe('debug mode', () => { test('can execute expression in debug mode', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index 30016f36d77d6..8edf6d3227c02 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -352,20 +352,30 @@ export class Execution< // actually have `then` or `subscribe` methods which would be treated as a `Promise` // or an `Observable` accordingly. return this.resolveArgs(fn, currentInput, fnArgs).pipe( - tap((args) => this.execution.params.debug && Object.assign(head.debug, { args })), - switchMap((args) => this.invokeFunction(fn, currentInput, args)), - switchMap((output) => (getType(output) === 'error' ? throwError(output) : of(output))), - tap((output) => this.execution.params.debug && Object.assign(head.debug, { output })), - switchMap((output) => this.invokeChain(tail, output)), - catchError((rawError) => { - const error = createError(rawError); - error.error.message = `[${fnName}] > ${error.error.message}`; - - if (this.execution.params.debug) { - Object.assign(head.debug, { error, rawError, success: false }); - } - - return of(error); + switchMap((resolvedArgs) => { + const args$ = isExpressionValueError(resolvedArgs) + ? throwError(resolvedArgs.error) + : of(resolvedArgs); + + return args$.pipe( + tap((args) => this.execution.params.debug && Object.assign(head.debug, { args })), + switchMap((args) => this.invokeFunction(fn, currentInput, args)), + switchMap((output) => + getType(output) === 'error' ? throwError(output) : of(output) + ), + tap((output) => this.execution.params.debug && Object.assign(head.debug, { output })), + switchMap((output) => this.invokeChain(tail, output)), + catchError((rawError) => { + const error = createError(rawError); + error.error.message = `[${fnName}] > ${error.error.message}`; + + if (this.execution.params.debug) { + Object.assign(head.debug, { error, rawError, success: false }); + } + + return of(error); + }) + ); }), finalize(() => { if (this.execution.params.debug) { @@ -449,7 +459,10 @@ export class Execution< } } - throw new Error(`Can not cast '${fromTypeName}' to any of '${toTypeNames.join(', ')}'`); + throw createError({ + name: 'invalid value', + message: `Can not cast '${fromTypeName}' to any of '${toTypeNames.join(', ')}'`, + }); } validate(value: Type, argDef: ExpressionFunctionParameter): void { @@ -459,7 +472,10 @@ export class Execution< }': '${argDef.options.join("', '")}'`; if (argDef.strict) { - throw new Error(message); + throw createError({ + message, + name: 'invalid argument', + }); } this.logger?.warn(message); @@ -471,7 +487,7 @@ export class Execution< fnDef: Fn, input: unknown, argAsts: Record - ): Observable> { + ): Observable | ExpressionValueError> { return defer(() => { const { args: argDefs } = fnDef; @@ -481,7 +497,10 @@ export class Execution< (acc, argAst, argName) => { const argDef = getByAlias(argDefs, argName); if (!argDef) { - throw new Error(`Unknown argument '${argName}' passed to function '${fnDef.name}'`); + throw createError({ + name: 'unknown argument', + message: `Unknown argument '${argName}' passed to function '${fnDef.name}'`, + }); } if (argDef.deprecated && !acc[argDef.name]) { this.logger?.warn(`Argument '${argName}' is deprecated in function '${fnDef.name}'`); @@ -502,7 +521,10 @@ export class Execution< continue; } - throw new Error(`${fnDef.name} requires the "${name}" argument`); + throw createError({ + name: 'missing argument', + message: `${fnDef.name} requires the "${name}" argument`, + }); } // Create the functions to resolve the argument ASTs into values @@ -513,14 +535,17 @@ export class Execution< (subInput = input) => this.interpret(item, subInput).pipe( pluck('result'), - map((output) => { + switchMap((output) => { if (isExpressionValueError(output)) { - throw output.error; + return of(output); } - return this.cast(output, argDefs[argName].types); - }), - tap((value) => this.validate(value, argDefs[argName])) + return of(output).pipe( + map((value) => this.cast(value, argDefs[argName].types)), + tap((value) => this.validate(value, argDefs[argName])), + catchError((error) => of(error)) + ); + }) ) ) ); @@ -531,7 +556,7 @@ export class Execution< return from([{}]); } - const resolvedArgValuesObservable = combineLatest( + return combineLatest( argNames.map((argName) => { const interpretFns = resolveArgFns[argName]; @@ -542,23 +567,25 @@ export class Execution< } return argDefs[argName].resolve - ? combineLatest(interpretFns.map((fn) => fn())) + ? combineLatest(interpretFns.map((fn) => fn())).pipe( + map((values) => values.find(isExpressionValueError) ?? values) + ) : of(interpretFns); }) - ); - - return resolvedArgValuesObservable.pipe( - map((resolvedArgValues) => - mapValues( - // Return an object here because the arguments themselves might actually have a 'then' - // function which would be treated as a promise - zipObject(argNames, resolvedArgValues), - // Just return the last unless the argument definition allows multiple - (argValues, argName) => (argDefs[argName].multi ? argValues : last(argValues)) - ) + ).pipe( + map( + (values) => + values.find(isExpressionValueError) ?? + mapValues( + // Return an object here because the arguments themselves might actually have a 'then' + // function which would be treated as a promise + zipObject(argNames, values as unknown[][]), + // Just return the last unless the argument definition allows multiple + (argValues, argName) => (argDefs[argName].multi ? argValues : last(argValues)) + ) ) ); - }); + }).pipe(catchError((error) => of(error))); } interpret(ast: ExpressionAstNode, input: T): Observable> { diff --git a/src/plugins/guided_onboarding/README.md b/src/plugins/guided_onboarding/README.md index 1daa04d223e2b..d54926da3c971 100755 --- a/src/plugins/guided_onboarding/README.md +++ b/src/plugins/guided_onboarding/README.md @@ -10,11 +10,9 @@ The client-side code registers a button in the Kibana header that controls the g ## Development -1. To enable the UI, add `guidedOnboarding.ui: true` to the file `KIBANA_FOLDER/config/kibana.dev.yml`. +1. Start Kibana with examples `yarn start --run-examples` to be able to see the guidedOnboardingExample plugin. -2. Start Kibana with examples `yarn start --run-examples` to be able to see the guidedOnboardingExample plugin. - -3. Navigate to `/app/guidedOnboardingExample` to start a guide and check the button in the header. +2. Navigate to `/app/guidedOnboardingExample` to start a guide and check the button in the header. ## API service *Also see `KIBANA_FOLDER/examples/guided_onboarding_example` for code examples.* diff --git a/src/plugins/guided_onboarding/kibana.json b/src/plugins/guided_onboarding/kibana.json index 42f893adfd63d..a7c1c3d217c1b 100755 --- a/src/plugins/guided_onboarding/kibana.json +++ b/src/plugins/guided_onboarding/kibana.json @@ -10,6 +10,5 @@ "server": true, "ui": true, "requiredBundles": ["kibanaReact"], - "optionalPlugins": [], - "configPath": ["guidedOnboarding"] + "optionalPlugins": ["cloud"] } diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts b/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts index 1ba565c9caee0..7e7f47670edf2 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts +++ b/src/plugins/guided_onboarding/public/components/guide_panel.styles.ts @@ -40,8 +40,12 @@ export const getGuidePanelStyles = (euiTheme: EuiThemeComputed) => ({ `, flyoutFooter: css` border-radius: 0 0 6px 6px; - background: ${euiTheme.colors.ghost}; + background: transparent; padding: 24px 30px; `, + flyoutFooterLink: css` + color: ${euiTheme.colors.darkShade}; + font-weight: 400; + `, }, }); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx index 0ef85523d01fe..83a030b385994 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -109,7 +109,8 @@ describe('Guided setup', () => { }); describe('Button component', () => { - test('should be disabled in there is no active guide', async () => { + // TODO check for the correct button behavior once https://github.com/elastic/kibana/issues/141129 is implemented + test.skip('should be disabled in there is no active guide', async () => { const { exists } = testBed; expect(exists('disabledGuideButton')).toBe(true); expect(exists('guideButton')).toBe(false); @@ -225,28 +226,109 @@ describe('Guided setup', () => { }); describe('Steps', () => { - test('should show "Start" button label if step has not been started', async () => { + const clickActiveStepButton = async () => { const { component, find } = testBed; + await act(async () => { + find('activeStepButton').simulate('click'); + }); + + component.update(); + }; + + test('can start a step if step has not been started', async () => { + const { component, find, exists } = testBed; + await updateComponentWithState(component, mockActiveSearchGuideState, true); - expect(find('activeStepButtonLabel').text()).toEqual('Start'); + expect(find('activeStepButton').text()).toEqual('Start'); + + await clickActiveStepButton(); + + expect(exists('guidePanel')).toBe(false); }); - test('should show "Continue" button label if step is in progress', async () => { - const { component, find } = testBed; + test('can continue a step if step is in progress', async () => { + const { component, find, exists } = testBed; await updateComponentWithState(component, mockInProgressSearchGuideState, true); - expect(find('activeStepButtonLabel').text()).toEqual('Continue'); + expect(find('activeStepButton').text()).toEqual('Continue'); + + await clickActiveStepButton(); + + expect(exists('guidePanel')).toBe(false); }); - test('shows "Mark done" button label if step is ready to complete', async () => { - const { component, find } = testBed; + test('can mark a step "done" if step is ready to complete', async () => { + const { component, find, exists } = testBed; await updateComponentWithState(component, mockReadyToCompleteSearchGuideState, true); - expect(find('activeStepButtonLabel').text()).toEqual('Mark done'); + expect(find('activeStepButton').text()).toEqual('Mark done'); + + await clickActiveStepButton(); + + // The guide panel should remain open after marking a step done + expect(exists('guidePanel')).toBe(true); + // Dependent on the Search guide config, which expects another step to start + expect(find('activeStepButton').text()).toEqual('Start'); + }); + + test('should render the step description as a paragraph if it is only one sentence', async () => { + const { component, find } = testBed; + + const mockSingleSentenceStepDescriptionGuideState: GuideState = { + guideId: 'observability', + isActive: true, + status: 'in_progress', + steps: [ + { + id: 'add_data', + status: 'complete', + }, + { + id: 'view_dashboard', + status: 'complete', + }, + { + id: 'tour_observability', + status: 'in_progress', + }, + ], + }; + + await updateComponentWithState( + component, + mockSingleSentenceStepDescriptionGuideState, + true + ); + + expect( + find('guidePanelStepDescription') + .last() + .containsMatchingElement( +

    {guidesConfig.observability.steps[2].descriptionList[0]}

    + ) + ).toBe(true); + }); + + test('should render the step description as an unordered list if it is more than one sentence', async () => { + const { component, find } = testBed; + + await updateComponentWithState(component, mockActiveSearchGuideState, true); + + expect( + find('guidePanelStepDescription') + .first() + .containsMatchingElement( +
      + {guidesConfig.search.steps[0].descriptionList.map((description, i) => ( +
    • {description}
    • + ))} +
    + ) + ).toBe(true); }); }); @@ -280,9 +362,8 @@ describe('Guided setup', () => { component.update(); expect(exists('quitGuideModal')).toBe(false); - // For now, the guide button is disabled once a user quits a guide - // This behavior will change once https://github.com/elastic/kibana/issues/141129 is implemented - expect(exists('disabledGuideButton')).toBe(true); + + // TODO check for the correct button behavior once https://github.com/elastic/kibana/issues/141129 is implemented }); test('cancels out of the quit guide confirmation modal', async () => { diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx index 6bd33735ec94e..def898cae8a6b 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx @@ -27,7 +27,6 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { ApplicationStart } from '@kbn/core/public'; import type { GuideState, GuideStep as GuideStepStatus } from '@kbn/guided-onboarding'; @@ -74,18 +73,19 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { const handleStepButtonClick = async (step: GuideStepStatus, stepConfig: StepConfig) => { if (guideState) { const { id, status } = step; + if (status === 'ready_to_complete') { return await api.completeGuideStep(guideState?.guideId, id); } - if (status === 'active') { - await api.startGuideStep(guideState!.guideId, id); - } if (status === 'active' || status === 'in_progress') { + await api.startGuideStep(guideState!.guideId, id); + if (stepConfig.location) { await application.navigateToApp(stepConfig.location.appID, { path: stepConfig.location.path, }); + if (stepConfig.manualCompletion?.readyToCompleteOnNavigation) { await api.completeGuideStep(guideState.guideId, id); } @@ -140,20 +140,8 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { // TODO handle loading, error state // https://github.com/elastic/kibana/issues/139799, https://github.com/elastic/kibana/issues/139798 if (!guideConfig) { - return ( - - {i18n.translate('guidedOnboarding.disabledGuidedSetupButtonLabel', { - defaultMessage: 'Setup guide', - })} - - ); + // TODO button show/hide logic https://github.com/elastic/kibana/issues/141129 + return null; } const stepsCompleted = getProgress(guideState); @@ -301,57 +289,59 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { - - - - {i18n.translate('guidedOnboarding.dropdownPanel.footer.exitGuideButtonLabel', { - defaultMessage: 'Quit setup guide', + + + + {i18n.translate('guidedOnboarding.dropdownPanel.footer.support', { + defaultMessage: 'Need help?', })} - - - - - {i18n.translate('guidedOnboarding.dropdownPanel.footer.feedbackLabel', { - defaultMessage: 'feedback', - })} - - ), - }} - /> + + + | - - - - - {i18n.translate( - 'guidedOnboarding.dropdownPanel.footer.helpTextDescription', - { - defaultMessage: 'here to help', - } - )} - - ), - }} - /> + + + {i18n.translate('guidedOnboarding.dropdownPanel.footer.feedback', { + defaultMessage: 'Give feedback', + })} + + + + + | + + + {i18n.translate('guidedOnboarding.dropdownPanel.footer.exitGuideButtonLabel', { + defaultMessage: 'Quit guide', + })} + + diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx index f79a26778c1a3..32ebd14dee806 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx @@ -93,13 +93,16 @@ export const GuideStep = ({ > <> - - -
      - {stepConfig.descriptionList.map((description, index) => { - return
    • {description}
    • ; - })} -
    + + {stepConfig.descriptionList.length === 1 ? ( +

    {stepConfig.descriptionList[0]}

    // If there is only one description, render it as a paragraph + ) : ( +
      + {stepConfig.descriptionList.map((description, index) => { + return
    • {description}
    • ; + })} +
    + )}
    @@ -109,7 +112,7 @@ export const GuideStep = ({ handleButtonClick()} fill - data-test-subj="activeStepButtonLabel" + data-test-subj="activeStepButton" > {getStepButtonLabel()} diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/index.ts b/src/plugins/guided_onboarding/public/constants/guides_config/index.ts index 9ce81cf9d4698..e2ab4f7e7747f 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/index.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/index.ts @@ -10,9 +10,11 @@ import type { GuidesConfig } from '../../types'; import { securityConfig } from './security'; import { observabilityConfig } from './observability'; import { searchConfig } from './search'; +import { testGuideConfig } from './test_guide'; export const guidesConfig: GuidesConfig = { security: securityConfig, observability: observabilityConfig, search: searchConfig, + testGuide: testGuideConfig, }; diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts b/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts index 76ee412dbd382..5862d0d132735 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts @@ -15,12 +15,12 @@ export const observabilityConfig: GuideConfig = { defaultMessage: 'Observe my Kubernetes infrastructure', }), description: i18n.translate('guidedOnboarding.observabilityGuide.description', { - defaultMessage: `We'll help you quickly gain visibility into your Kubernetes environment using Elastic's out-of-the-box integration. Gain deep insights from your logs, metrics, and traces, and proactively detect issues and take action to resolve issues.`, + defaultMessage: `We'll help you quickly get visibility into your Kubernetes environment with our Elastic integration. Gain deep insights from your logs, metrics, and traces to proactively detect issues and take action to resolve them.`, }), guideName: 'Kubernetes', docs: { text: i18n.translate('guidedOnboarding.observabilityGuide.documentationLink', { - defaultMessage: 'Kubernetes documentation', + defaultMessage: 'Learn more', }), url: 'https://docs.elastic.co/en/integrations/kubernetes', }, @@ -28,7 +28,7 @@ export const observabilityConfig: GuideConfig = { { id: 'add_data', title: i18n.translate('guidedOnboarding.observabilityGuide.addDataStep.title', { - defaultMessage: 'Add data', + defaultMessage: 'Add and verify your data', }), integration: 'kubernetes', descriptionList: [ @@ -59,13 +59,13 @@ export const observabilityConfig: GuideConfig = { title: i18n.translate( 'guidedOnboarding.observabilityGuide.viewDashboardStep.manualCompletionPopoverTitle', { - defaultMessage: 'Explore the pre-built Kubernetes dashboards', + defaultMessage: 'Explore Kubernetes dashboards', } ), description: i18n.translate( 'guidedOnboarding.observabilityGuide.viewDashboardStep.manualCompletionPopoverDescription', { - defaultMessage: `Take your time to explore out-of-the-box dashboards that are included with the Kubernetes integration. When you're ready, you can access the next step of the guide in the button above.`, + defaultMessage: `Take your time to explore these pre-built dashboards included with the Kubernetes integration. When you’re ready, click the Setup guide button to continue.`, } ), readyToCompleteOnNavigation: true, diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/security.ts b/src/plugins/guided_onboarding/public/constants/guides_config/security.ts index d2f9b352b9d81..3930ab66220f0 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/security.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/security.ts @@ -6,25 +6,34 @@ * Side Public License, v 1. */ +import { i18n } from '@kbn/i18n'; import type { GuideConfig } from '../../types'; export const securityConfig: GuideConfig = { - title: 'Get started with SIEM', + title: i18n.translate('guidedOnboarding.securityGuide.title', { + defaultMessage: 'Elastic Security guided setup', + }), guideName: 'Security', completedGuideRedirectLocation: { appID: 'security', path: '/app/security/dashboards', }, - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + description: i18n.translate('guidedOnboarding.securityGuide.description', { + defaultMessage: `We'll help you get set up quickly, using Elastic's out-of-the-box integrations.`, + }), steps: [ { id: 'add_data', - title: 'Add and view your data', + title: i18n.translate('guidedOnboarding.securityGuide.addDataStep.title', { + defaultMessage: 'Add data with Elastic Defend', + }), descriptionList: [ - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', - 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', - 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + i18n.translate('guidedOnboarding.securityGuide.addDataStep.description1', { + defaultMessage: 'Select the Elastic Defend integration to add your data.', + }), + i18n.translate('guidedOnboarding.securityGuide.addDataStep.description2', { + defaultMessage: 'Make sure your data looks good.', + }), ], integration: 'endpoint', location: { @@ -34,16 +43,27 @@ export const securityConfig: GuideConfig = { }, { id: 'rules', - title: 'Turn on rules', + title: i18n.translate('guidedOnboarding.securityGuide.rulesStep.title', { + defaultMessage: 'Turn on rules', + }), descriptionList: [ - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', - 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', - 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + i18n.translate('guidedOnboarding.securityGuide.rulesStep.description1', { + defaultMessage: 'Load the prebuilt rules.', + }), + i18n.translate('guidedOnboarding.securityGuide.rulesStep.description2', { + defaultMessage: 'Select the rules that you want.', + }), ], manualCompletion: { - title: 'Manual completion step title', - description: - 'Mark the step complete by opening the panel and clicking the button "Mark done"', + title: i18n.translate('guidedOnboarding.securityGuide.rulesStep.manualCompletion.title', { + defaultMessage: 'Continue with the tour', + }), + description: i18n.translate( + 'guidedOnboarding.securityGuide.rulesStep.manualCompletion.description', + { + defaultMessage: 'After you’ve enabled the rules you want, click here to continue.', + } + ), }, location: { appID: 'securitySolutionUI', @@ -52,11 +72,16 @@ export const securityConfig: GuideConfig = { }, { id: 'alertsCases', - title: 'Alerts and cases', + title: i18n.translate('guidedOnboarding.securityGuide.alertsStep.title', { + defaultMessage: 'Manage alerts and cases', + }), descriptionList: [ - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', - 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', - 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', + i18n.translate('guidedOnboarding.securityGuide.alertsStep.description1', { + defaultMessage: 'View and triage alerts.', + }), + i18n.translate('guidedOnboarding.securityGuide.alertsStep.description2', { + defaultMessage: 'Create a case.', + }), ], location: { appID: 'securitySolutionUI', diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts b/src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts new file mode 100644 index 0000000000000..b357ad497c6b4 --- /dev/null +++ b/src/plugins/guided_onboarding/public/constants/guides_config/test_guide.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 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 or the Server + * Side Public License, v 1. + */ + +import type { GuideConfig } from '../../types'; + +export const testGuideConfig: GuideConfig = { + title: 'Test guide for development', + description: `This guide is used to test the guided onboarding UI while in development and to run automated tests for the API and UI components.`, + guideName: 'Testing example', + docs: { + text: 'Testing example docs', + url: 'example.com', + }, + steps: [ + { + id: 'step1', + title: 'Step 1 (completed via an API request)', + descriptionList: [ + `This step is directly completed by clicking the button that uses the API function 'completeGuideStep`, + 'Navigate to /guidedOnboardingExample/stepOne to complete the step.', + ], + location: { + appID: 'guidedOnboardingExample', + path: 'stepOne', + }, + integration: 'testIntegration', + }, + { + id: 'step2', + title: 'Step 2 (manual completion after navigation)', + descriptionList: [ + 'This step is set to ready_to_complete on page navigation.', + 'After that click the popover on the guide button in the header and mark the step done', + ], + location: { + appID: 'guidedOnboardingExample', + path: 'stepTwo', + }, + manualCompletion: { + title: 'Manual completion step title', + description: + 'Mark the step complete by opening the panel and clicking the button "Mark done"', + readyToCompleteOnNavigation: true, + }, + }, + { + id: 'step3', + title: 'Step 3 (manual completion after click)', + descriptionList: [ + 'This step is completed by clicking a button on the page and then clicking the popover on the guide button in the header and marking the step done', + ], + manualCompletion: { + title: 'Manual completion step title', + description: + 'Mark the step complete by opening the panel and clicking the button "Mark done"', + }, + location: { + appID: 'guidedOnboardingExample', + path: 'stepThree', + }, + }, + ], +}; diff --git a/src/plugins/guided_onboarding/public/index.ts b/src/plugins/guided_onboarding/public/index.ts index f4b2e6d8ff2f8..c9ea467231981 100755 --- a/src/plugins/guided_onboarding/public/index.ts +++ b/src/plugins/guided_onboarding/public/index.ts @@ -6,11 +6,10 @@ * Side Public License, v 1. */ -import { PluginInitializerContext } from '@kbn/core/public'; import { GuidedOnboardingPlugin } from './plugin'; -export function plugin(ctx: PluginInitializerContext) { - return new GuidedOnboardingPlugin(ctx); +export function plugin() { + return new GuidedOnboardingPlugin(); } export type { GuidedOnboardingPluginSetup, diff --git a/src/plugins/guided_onboarding/public/plugin.tsx b/src/plugins/guided_onboarding/public/plugin.tsx index f74e19a03300f..4cf5fa9749a07 100755 --- a/src/plugins/guided_onboarding/public/plugin.tsx +++ b/src/plugins/guided_onboarding/public/plugin.tsx @@ -10,18 +10,11 @@ import ReactDOM from 'react-dom'; import React from 'react'; import * as Rx from 'rxjs'; import { I18nProvider } from '@kbn/i18n-react'; -import { - CoreSetup, - CoreStart, - Plugin, - CoreTheme, - ApplicationStart, - PluginInitializerContext, -} from '@kbn/core/public'; +import { CoreSetup, CoreStart, Plugin, CoreTheme, ApplicationStart } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import type { - ClientConfigType, + AppPluginStartDependencies, GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart, } from './types'; @@ -31,32 +24,33 @@ import { ApiService, apiService } from './services/api'; export class GuidedOnboardingPlugin implements Plugin { - constructor(private ctx: PluginInitializerContext) {} + constructor() {} public setup(core: CoreSetup): GuidedOnboardingPluginSetup { return {}; } - public start(core: CoreStart): GuidedOnboardingPluginStart { - const { ui: isGuidedOnboardingUiEnabled } = this.ctx.config.get(); - if (!isGuidedOnboardingUiEnabled) { - return {}; - } - + public start( + core: CoreStart, + { cloud }: AppPluginStartDependencies + ): GuidedOnboardingPluginStart { const { chrome, http, theme, application } = core; // Initialize services apiService.setup(http); - chrome.navControls.registerExtension({ - order: 1000, - mount: (target) => - this.mount({ - targetDomElement: target, - theme$: theme.theme$, - api: apiService, - application, - }), - }); + // Guided onboarding UI is only available on cloud + if (cloud?.isCloudEnabled) { + chrome.navControls.registerExtension({ + order: 1000, + mount: (target) => + this.mount({ + targetDomElement: target, + theme$: theme.theme$, + api: apiService, + application, + }), + }); + } // Return methods that should be available to other plugins return { diff --git a/src/plugins/guided_onboarding/public/services/api.mocks.ts b/src/plugins/guided_onboarding/public/services/api.mocks.ts index 21bb257cad68f..2294607f91b38 100644 --- a/src/plugins/guided_onboarding/public/services/api.mocks.ts +++ b/src/plugins/guided_onboarding/public/services/api.mocks.ts @@ -6,84 +6,78 @@ * Side Public License, v 1. */ -import type { GuideState } from '@kbn/guided-onboarding'; +import type { GuideState, GuideId, GuideStepIds } from '@kbn/guided-onboarding'; -export const searchAddDataActiveState: GuideState = { - guideId: 'search', +export const testGuide: GuideId = 'testGuide'; +export const testGuideFirstStep: GuideStepIds = 'step1'; +export const testGuideManualCompletionStep = 'step2'; +export const testGuideLastStep: GuideStepIds = 'step3'; +export const testIntegration = 'testIntegration'; +export const wrongIntegration = 'notTestIntegration'; + +export const testGuideStep1ActiveState: GuideState = { + guideId: 'testGuide', isActive: true, status: 'in_progress', steps: [ { - id: 'add_data', + id: 'step1', status: 'active', }, { - id: 'browse_docs', + id: 'step2', status: 'inactive', }, { - id: 'search_experience', + id: 'step3', status: 'inactive', }, ], }; -export const securityAddDataInProgressState: GuideState = { - guideId: 'security', - status: 'in_progress', - isActive: true, +export const testGuideStep1InProgressState: GuideState = { + ...testGuideStep1ActiveState, steps: [ { - id: 'add_data', - status: 'in_progress', - }, - { - id: 'rules', - status: 'inactive', - }, - { - id: 'alertsCases', - status: 'inactive', + id: testGuideStep1ActiveState.steps[0].id, + status: 'in_progress', // update the first step status }, + testGuideStep1ActiveState.steps[1], + testGuideStep1ActiveState.steps[2], ], }; -export const securityRulesActiveState: GuideState = { - guideId: 'security', - isActive: true, - status: 'in_progress', +export const testGuideStep2ActiveState: GuideState = { + ...testGuideStep1ActiveState, steps: [ { - id: 'add_data', + ...testGuideStep1ActiveState.steps[0], status: 'complete', }, { - id: 'rules', + id: testGuideStep1ActiveState.steps[1].id, status: 'active', }, - { - id: 'alertsCases', - status: 'inactive', - }, + testGuideStep1ActiveState.steps[2], ], }; -export const noGuideActiveState: GuideState = { - guideId: 'security', - status: 'in_progress', - isActive: false, +export const testGuideStep2InProgressState: GuideState = { + ...testGuideStep1ActiveState, steps: [ { - id: 'add_data', - status: 'in_progress', - }, - { - id: 'rules', - status: 'inactive', + ...testGuideStep1ActiveState.steps[0], + status: 'complete', }, { - id: 'alertsCases', - status: 'inactive', + id: testGuideStep1ActiveState.steps[1].id, + status: 'in_progress', }, + testGuideStep1ActiveState.steps[2], ], }; + +export const testGuideNotActiveState: GuideState = { + ...testGuideStep1ActiveState, + isActive: false, +}; diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts index 3503b68e5466c..56a5755f0ee55 100644 --- a/src/plugins/guided_onboarding/public/services/api.test.ts +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -12,20 +12,20 @@ import type { GuideState } from '@kbn/guided-onboarding'; import { firstValueFrom, Subscription } from 'rxjs'; import { API_BASE_PATH } from '../../common/constants'; -import { guidesConfig } from '../constants/guides_config'; import { ApiService } from './api'; import { - noGuideActiveState, - searchAddDataActiveState, - securityAddDataInProgressState, - securityRulesActiveState, + testGuide, + testGuideFirstStep, + testGuideManualCompletionStep, + testGuideStep1ActiveState, + testGuideStep1InProgressState, + testGuideStep2ActiveState, + testGuideNotActiveState, + testIntegration, + wrongIntegration, + testGuideStep2InProgressState, } from './api.mocks'; -const searchGuide = 'search'; -const firstStep = guidesConfig[searchGuide].steps[0].id; -const endpointIntegration = 'endpoint'; -const kubernetesIntegration = 'kubernetes'; - describe('GuidedOnboarding ApiService', () => { let httpClient: jest.Mocked; let apiService: ApiService; @@ -34,7 +34,7 @@ describe('GuidedOnboarding ApiService', () => { beforeEach(() => { httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); httpClient.get.mockResolvedValue({ - state: [securityAddDataInProgressState], + state: [testGuideStep1ActiveState], }); apiService = new ApiService(); apiService.setup(httpClient); @@ -57,10 +57,10 @@ describe('GuidedOnboarding ApiService', () => { }); it('broadcasts the updated state', async () => { - await apiService.activateGuide(searchGuide, searchAddDataActiveState); + await apiService.activateGuide(testGuide, testGuideStep1ActiveState); const state = await firstValueFrom(apiService.fetchActiveGuideState$()); - expect(state).toEqual(searchAddDataActiveState); + expect(state).toEqual(testGuideStep1ActiveState); }); }); @@ -74,12 +74,12 @@ describe('GuidedOnboarding ApiService', () => { describe('deactivateGuide', () => { it('deactivates an existing guide', async () => { - await apiService.deactivateGuide(searchAddDataActiveState); + await apiService.deactivateGuide(testGuideStep1ActiveState); expect(httpClient.put).toHaveBeenCalledTimes(1); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { body: JSON.stringify({ - ...searchAddDataActiveState, + ...testGuideStep1ActiveState, isActive: false, }), }); @@ -88,17 +88,7 @@ describe('GuidedOnboarding ApiService', () => { describe('updateGuideState', () => { it('sends a request to the put API', async () => { - const updatedState: GuideState = { - ...searchAddDataActiveState, - steps: [ - { - id: searchAddDataActiveState.steps[0].id, - status: 'in_progress', // update the first step status - }, - searchAddDataActiveState.steps[1], - searchAddDataActiveState.steps[2], - ], - }; + const updatedState: GuideState = testGuideStep1InProgressState; await apiService.updateGuideState(updatedState, false); expect(httpClient.put).toHaveBeenCalledTimes(1); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { @@ -108,22 +98,12 @@ describe('GuidedOnboarding ApiService', () => { }); describe('isGuideStepActive$', () => { - it('returns true if the step has been started', async (done) => { - const updatedState: GuideState = { - ...searchAddDataActiveState, - steps: [ - { - id: searchAddDataActiveState.steps[0].id, - status: 'in_progress', - }, - searchAddDataActiveState.steps[1], - searchAddDataActiveState.steps[2], - ], - }; - await apiService.updateGuideState(updatedState, false); + it('returns true if the step has been started', (done) => { + const updatedState: GuideState = testGuideStep1InProgressState; + apiService.updateGuideState(updatedState, false); subscription = apiService - .isGuideStepActive$(searchGuide, firstStep) + .isGuideStepActive$(testGuide, testGuideFirstStep) .subscribe((isStepActive) => { if (isStepActive) { done(); @@ -131,10 +111,9 @@ describe('GuidedOnboarding ApiService', () => { }); }); - it('returns false if the step is not been started', async (done) => { - await apiService.updateGuideState(searchAddDataActiveState, false); + it('returns false if the step is not been started', (done) => { subscription = apiService - .isGuideStepActive$(searchGuide, firstStep) + .isGuideStepActive$(testGuide, testGuideFirstStep) .subscribe((isStepActive) => { if (!isStepActive) { done(); @@ -145,56 +124,44 @@ describe('GuidedOnboarding ApiService', () => { describe('activateGuide', () => { it('activates a new guide', async () => { - await apiService.activateGuide(searchGuide); + // update the mock to no active guides + httpClient.get.mockResolvedValue({ + state: [], + }); + apiService.setup(httpClient); + + await apiService.activateGuide(testGuide); expect(httpClient.put).toHaveBeenCalledTimes(1); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify({ - isActive: true, - status: 'not_started', - steps: [ - { - id: 'add_data', - status: 'active', - }, - { - id: 'browse_docs', - status: 'inactive', - }, - { - id: 'search_experience', - status: 'inactive', - }, - ], - guideId: searchGuide, - }), + body: JSON.stringify({ ...testGuideStep1ActiveState, status: 'not_started' }), }); }); it('reactivates a guide that has already been started', async () => { - await apiService.activateGuide(searchGuide, searchAddDataActiveState); + await apiService.activateGuide(testGuide, testGuideStep1ActiveState); expect(httpClient.put).toHaveBeenCalledTimes(1); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify(searchAddDataActiveState), + body: JSON.stringify(testGuideStep1ActiveState), }); }); }); describe('completeGuide', () => { const readyToCompleteGuideState: GuideState = { - ...searchAddDataActiveState, + ...testGuideStep1ActiveState, steps: [ { - id: 'add_data', + ...testGuideStep1ActiveState.steps[0], status: 'complete', }, { - id: 'browse_docs', + ...testGuideStep1ActiveState.steps[1], status: 'complete', }, { - id: 'search_experience', + ...testGuideStep1ActiveState.steps[2], status: 'complete', }, ], @@ -205,7 +172,7 @@ describe('GuidedOnboarding ApiService', () => { }); it('updates the selected guide and marks it as complete', async () => { - await apiService.completeGuide(searchGuide); + await apiService.completeGuide(testGuide); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { body: JSON.stringify({ @@ -223,51 +190,39 @@ describe('GuidedOnboarding ApiService', () => { it('returns undefined if the selected guide has uncompleted steps', async () => { const incompleteGuideState: GuideState = { - ...searchAddDataActiveState, + ...testGuideStep1ActiveState, steps: [ { - id: 'add_data', + ...testGuideStep1ActiveState.steps[0], status: 'complete', }, { - id: 'browse_docs', + ...testGuideStep1ActiveState.steps[1], status: 'complete', }, { - id: 'search_experience', + ...testGuideStep1ActiveState.steps[2], status: 'in_progress', }, ], }; await apiService.updateGuideState(incompleteGuideState, false); - const completedState = await apiService.completeGuide(searchGuide); + const completedState = await apiService.completeGuide(testGuide); expect(completedState).not.toBeDefined(); }); }); describe('startGuideStep', () => { beforeEach(async () => { - await apiService.updateGuideState(searchAddDataActiveState, false); + await apiService.updateGuideState(testGuideStep1ActiveState, false); }); it('updates the selected step and marks it as in_progress', async () => { - await apiService.startGuideStep(searchGuide, firstStep); + await apiService.startGuideStep(testGuide, testGuideFirstStep); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify({ - ...searchAddDataActiveState, - isActive: true, - status: 'in_progress', - steps: [ - { - id: searchAddDataActiveState.steps[0].id, - status: 'in_progress', - }, - searchAddDataActiveState.steps[1], - searchAddDataActiveState.steps[2], - ], - }), + body: JSON.stringify(testGuideStep1InProgressState), }); }); @@ -279,76 +234,35 @@ describe('GuidedOnboarding ApiService', () => { describe('completeGuideStep', () => { it(`completes the step when it's in progress`, async () => { - const updatedState: GuideState = { - ...searchAddDataActiveState, - steps: [ - { - id: searchAddDataActiveState.steps[0].id, - status: 'in_progress', // Mark a step as in_progress in order to test the "completeGuideStep" behavior - }, - searchAddDataActiveState.steps[1], - searchAddDataActiveState.steps[2], - ], - }; - await apiService.updateGuideState(updatedState, false); + await apiService.updateGuideState(testGuideStep1InProgressState, false); - await apiService.completeGuideStep(searchGuide, firstStep); + await apiService.completeGuideStep(testGuide, testGuideFirstStep); // Once on update, once on complete expect(httpClient.put).toHaveBeenCalledTimes(2); // Verify the completed step now has a "complete" status, and the subsequent step is "active" expect(httpClient.put).toHaveBeenLastCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify({ - ...updatedState, - steps: [ - { - id: searchAddDataActiveState.steps[0].id, - status: 'complete', - }, - { - id: searchAddDataActiveState.steps[1].id, - status: 'active', - }, - searchAddDataActiveState.steps[2], - ], - }), + body: JSON.stringify({ ...testGuideStep2ActiveState }), }); }); it(`marks the step as 'ready_to_complete' if it's configured for manual completion`, async () => { - const securityRulesInProgressState = { - ...securityRulesActiveState, - steps: [ - securityRulesActiveState.steps[0], - { - id: securityRulesActiveState.steps[1].id, - status: 'in_progress', - }, - securityRulesActiveState.steps[2], - ], - }; httpClient.get.mockResolvedValue({ - state: [securityRulesInProgressState], + state: [testGuideStep2InProgressState], }); apiService.setup(httpClient); - await apiService.completeGuideStep('security', 'rules'); + await apiService.completeGuideStep(testGuide, testGuideManualCompletionStep); expect(httpClient.put).toHaveBeenCalledTimes(1); // Verify the completed step now has a "ready_to_complete" status, and the subsequent step is "inactive" expect(httpClient.put).toHaveBeenLastCalledWith(`${API_BASE_PATH}/state`, { body: JSON.stringify({ - ...securityRulesInProgressState, + ...testGuideStep2InProgressState, steps: [ - securityRulesInProgressState.steps[0], - { - id: securityRulesInProgressState.steps[1].id, - status: 'ready_to_complete', - }, - { - id: securityRulesInProgressState.steps[2].id, - status: 'inactive', - }, + testGuideStep2InProgressState.steps[0], + { ...testGuideStep2InProgressState.steps[1], status: 'ready_to_complete' }, + testGuideStep2InProgressState.steps[2], ], }), }); @@ -360,24 +274,20 @@ describe('GuidedOnboarding ApiService', () => { }); it('does nothing if the step is not in progress', async () => { - httpClient.get.mockResolvedValue({ - state: [searchAddDataActiveState], - }); - apiService.setup(httpClient); - - await apiService.completeGuideStep(searchGuide, firstStep); + // by default the state set in beforeEach is test guide, step 1 active + await apiService.completeGuideStep(testGuide, testGuideFirstStep); expect(httpClient.put).toHaveBeenCalledTimes(0); }); }); describe('isGuidedOnboardingActiveForIntegration$', () => { - it('returns true if the integration is part of the active step', async (done) => { + it('returns true if the integration is part of the active step', (done) => { httpClient.get.mockResolvedValue({ - state: [securityAddDataInProgressState], + state: [testGuideStep1InProgressState], }); apiService.setup(httpClient); subscription = apiService - .isGuidedOnboardingActiveForIntegration$(endpointIntegration) + .isGuidedOnboardingActiveForIntegration$(testIntegration) .subscribe((isIntegrationInGuideStep) => { if (isIntegrationInGuideStep) { done(); @@ -385,13 +295,13 @@ describe('GuidedOnboarding ApiService', () => { }); }); - it('returns false if another integration is part of the active step', async (done) => { + it('returns false if the current step has a different integration', (done) => { httpClient.get.mockResolvedValue({ - state: [securityAddDataInProgressState], + state: [testGuideStep1InProgressState], }); apiService.setup(httpClient); subscription = apiService - .isGuidedOnboardingActiveForIntegration$(kubernetesIntegration) + .isGuidedOnboardingActiveForIntegration$(wrongIntegration) .subscribe((isIntegrationInGuideStep) => { if (!isIntegrationInGuideStep) { done(); @@ -399,13 +309,13 @@ describe('GuidedOnboarding ApiService', () => { }); }); - it('returns false if no guide is active', async (done) => { + it('returns false if no guide is active', (done) => { httpClient.get.mockResolvedValue({ - state: [noGuideActiveState], + state: [testGuideNotActiveState], }); apiService.setup(httpClient); subscription = apiService - .isGuidedOnboardingActiveForIntegration$(endpointIntegration) + .isGuidedOnboardingActiveForIntegration$(testIntegration) .subscribe((isIntegrationInGuideStep) => { if (!isIntegrationInGuideStep) { done(); @@ -417,35 +327,35 @@ describe('GuidedOnboarding ApiService', () => { describe('completeGuidedOnboardingForIntegration', () => { it(`completes the step if it's active for the integration`, async () => { httpClient.get.mockResolvedValue({ - state: [securityAddDataInProgressState], + state: [testGuideStep1InProgressState], }); apiService.setup(httpClient); - await apiService.completeGuidedOnboardingForIntegration(endpointIntegration); + await apiService.completeGuidedOnboardingForIntegration(testIntegration); expect(httpClient.put).toHaveBeenCalledTimes(1); // this assertion depends on the guides config expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify(securityRulesActiveState), + body: JSON.stringify(testGuideStep2ActiveState), }); }); it(`does nothing if the step has a different integration`, async () => { httpClient.get.mockResolvedValue({ - state: [securityAddDataInProgressState], + state: [testGuideStep1InProgressState], }); apiService.setup(httpClient); - await apiService.completeGuidedOnboardingForIntegration(kubernetesIntegration); + await apiService.completeGuidedOnboardingForIntegration(wrongIntegration); expect(httpClient.put).not.toHaveBeenCalled(); }); it(`does nothing if no guide is active`, async () => { httpClient.get.mockResolvedValue({ - state: [noGuideActiveState], + state: [testGuideNotActiveState], }); apiService.setup(httpClient); - await apiService.completeGuidedOnboardingForIntegration(endpointIntegration); + await apiService.completeGuidedOnboardingForIntegration(testIntegration); expect(httpClient.put).not.toHaveBeenCalled(); }); }); diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index 688e72fa83243..cd33f9505c546 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -147,10 +147,10 @@ export class ApiService implements GuidedOnboardingApi { }); const updatedGuide: GuideState = { + guideId, isActive: true, status: 'not_started', steps: updatedSteps, - guideId, }; return await this.updateGuideState(updatedGuide, true); diff --git a/src/plugins/guided_onboarding/public/services/helpers.test.ts b/src/plugins/guided_onboarding/public/services/helpers.test.ts index 9dc7519a02019..82720c4f9d223 100644 --- a/src/plugins/guided_onboarding/public/services/helpers.test.ts +++ b/src/plugins/guided_onboarding/public/services/helpers.test.ts @@ -6,51 +6,50 @@ * Side Public License, v 1. */ -import { guidesConfig } from '../constants/guides_config'; import { isIntegrationInGuideStep, isLastStep } from './helpers'; import { - noGuideActiveState, - securityAddDataInProgressState, - securityRulesActiveState, + testGuide, + testGuideFirstStep, + testGuideLastStep, + testGuideNotActiveState, + testGuideStep1InProgressState, + testGuideStep2InProgressState, + testIntegration, + wrongIntegration, } from './api.mocks'; -const searchGuide = 'search'; -const firstStep = guidesConfig[searchGuide].steps[0].id; -const lastStep = guidesConfig[searchGuide].steps[guidesConfig[searchGuide].steps.length - 1].id; - describe('GuidedOnboarding ApiService helpers', () => { - // this test suite depends on the guides config describe('isLastStepActive', () => { it('returns true if the passed params are for the last step', () => { - const result = isLastStep(searchGuide, lastStep); + const result = isLastStep(testGuide, testGuideLastStep); expect(result).toBe(true); }); it('returns false if the passed params are not for the last step', () => { - const result = isLastStep(searchGuide, firstStep); + const result = isLastStep(testGuide, testGuideFirstStep); expect(result).toBe(false); }); }); describe('isIntegrationInGuideStep', () => { it('return true if the integration is defined in the guide step config', () => { - const result = isIntegrationInGuideStep(securityAddDataInProgressState, 'endpoint'); + const result = isIntegrationInGuideStep(testGuideStep1InProgressState, testIntegration); expect(result).toBe(true); }); it('returns false if a different integration is defined in the guide step', () => { - const result = isIntegrationInGuideStep(securityAddDataInProgressState, 'kubernetes'); + const result = isIntegrationInGuideStep(testGuideStep1InProgressState, wrongIntegration); expect(result).toBe(false); }); it('returns false if no integration is defined in the guide step', () => { - const result = isIntegrationInGuideStep(securityRulesActiveState, 'endpoint'); + const result = isIntegrationInGuideStep(testGuideStep2InProgressState, testIntegration); expect(result).toBe(false); }); it('returns false if no guide is active', () => { - const result = isIntegrationInGuideStep(noGuideActiveState, 'endpoint'); + const result = isIntegrationInGuideStep(testGuideNotActiveState, testIntegration); expect(result).toBe(false); }); it('returns false if no integration passed', () => { - const result = isIntegrationInGuideStep(securityAddDataInProgressState); + const result = isIntegrationInGuideStep(testGuideStep1InProgressState); expect(result).toBe(false); }); }); diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts index 41a26c6c32de1..3ff0507c494dc 100755 --- a/src/plugins/guided_onboarding/public/types.ts +++ b/src/plugins/guided_onboarding/public/types.ts @@ -9,6 +9,7 @@ import { Observable } from 'rxjs'; import { HttpSetup } from '@kbn/core/public'; import type { GuideState, GuideId, GuideStepIds, StepStatus } from '@kbn/guided-onboarding'; +import type { CloudStart } from '@kbn/cloud-plugin/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface GuidedOnboardingPluginSetup {} @@ -17,8 +18,8 @@ export interface GuidedOnboardingPluginStart { guidedOnboardingApi?: GuidedOnboardingApi; } -export interface ClientConfigType { - ui: boolean; +export interface AppPluginStartDependencies { + cloud?: CloudStart; } export interface GuidedOnboardingApi { diff --git a/src/plugins/guided_onboarding/server/config.ts b/src/plugins/guided_onboarding/server/config.ts deleted file mode 100644 index 61f45be14f4e9..0000000000000 --- a/src/plugins/guided_onboarding/server/config.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 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 or the Server - * Side Public License, v 1. - */ - -import { PluginConfigDescriptor } from '@kbn/core/server'; -import { schema, TypeOf } from '@kbn/config-schema'; - -// By default, hide any guided onboarding UI. Change it with guidedOnboarding.ui:true in kibana.dev.yml -const configSchema = schema.object({ - ui: schema.boolean({ defaultValue: false }), -}); - -export type GuidedOnboardingConfig = TypeOf; - -export const config: PluginConfigDescriptor = { - // define which config properties should be available in the client side plugin - exposeToBrowser: { - ui: true, - }, - schema: configSchema, -}; diff --git a/src/plugins/guided_onboarding/server/index.ts b/src/plugins/guided_onboarding/server/index.ts index c7a7fff53656d..12eb46043cf23 100755 --- a/src/plugins/guided_onboarding/server/index.ts +++ b/src/plugins/guided_onboarding/server/index.ts @@ -9,8 +9,6 @@ import { PluginInitializerContext } from '@kbn/core/server'; import { GuidedOnboardingPlugin } from './plugin'; -export { config } from './config'; - export function plugin(initializerContext: PluginInitializerContext) { return new GuidedOnboardingPlugin(initializerContext); } diff --git a/src/plugins/guided_onboarding/tsconfig.json b/src/plugins/guided_onboarding/tsconfig.json index 4a024443419ad..2837b97459430 100644 --- a/src/plugins/guided_onboarding/tsconfig.json +++ b/src/plugins/guided_onboarding/tsconfig.json @@ -15,5 +15,6 @@ { "path": "../kibana_react/tsconfig.json" }, + { "path": "../../../x-pack/plugins/cloud/tsconfig.json" }, ] } diff --git a/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap b/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap index 3cc05cb41c6f9..53df35833013f 100644 --- a/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap +++ b/src/plugins/home/public/application/components/__snapshots__/home.test.tsx.snap @@ -28,8 +28,10 @@ exports[`home change home route should render a link to change the default route "integrations": true, }, }, + "navigateToUrl": [MockFunction], } } + isCloudEnabled={false} isDarkMode={false} />

    @@ -37,8 +37,6 @@ exports[`AddData render 1`] = ` { addBasePath={addBasePathMock} application={applicationStartMock} isDarkMode={false} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); diff --git a/src/plugins/home/public/application/components/add_data/add_data.tsx b/src/plugins/home/public/application/components/add_data/add_data.tsx index 27f98a85ff4e8..a3cdbd9241020 100644 --- a/src/plugins/home/public/application/components/add_data/add_data.tsx +++ b/src/plugins/home/public/application/components/add_data/add_data.tsx @@ -29,9 +29,10 @@ interface Props { addBasePath: (path: string) => string; application: ApplicationStart; isDarkMode: boolean; + isCloudEnabled: boolean; } -export const AddData: FC = ({ addBasePath, application, isDarkMode }) => { +export const AddData: FC = ({ addBasePath, application, isDarkMode, isCloudEnabled }) => { const { trackUiMetric } = getServices(); const canAccessIntegrations = application.capabilities.navLinks.integrations; if (canAccessIntegrations) { @@ -59,26 +60,47 @@ export const AddData: FC = ({ addBasePath, application, isDarkMode }) =>

    - + + {isCloudEnabled && ( + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + { + trackUiMetric(METRIC_TYPE.CLICK, 'guided_onboarding_link'); + }} + > + + + + )} {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} { trackUiMetric(METRIC_TYPE.CLICK, 'home_tutorial_directory'); createAppNavigationHandler('/app/integrations/browse')(event); }} + fullWidth > - + - + - + - + { const { chromeServiceMock, applicationServiceMock } = jest.requireActual('@kbn/core/public/mocks'); const { uiSettingsServiceMock } = jest.requireActual('@kbn/core-ui-settings-browser-mocks'); + const { cloudMock } = jest.requireActual('@kbn/cloud-plugin/public/mocks'); const uiSettingsMock = uiSettingsServiceMock.createSetupContract(); uiSettingsMock.get.mockReturnValue(false); return { getServices: () => ({ + cloud: cloudMock.createSetup(), chrome: chromeServiceMock.createStartContract(), application: applicationServiceMock.createStartContract(), trackUiMetric: jest.fn(), diff --git a/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx b/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx index e63676ca3ca72..c82b200dfb6bd 100644 --- a/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx +++ b/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx @@ -21,6 +21,7 @@ import { } from '@elastic/eui'; import { css } from '@emotion/react'; +import { useHistory } from 'react-router-dom'; import { METRIC_TYPE } from '@kbn/analytics'; import { i18n } from '@kbn/i18n'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; @@ -45,9 +46,10 @@ const skipText = i18n.translate('home.guidedOnboarding.gettingStarted.skip.butto }); export const GettingStarted = () => { - const { application, trackUiMetric, chrome, guidedOnboardingService, http, uiSettings } = + const { application, trackUiMetric, chrome, guidedOnboardingService, http, uiSettings, cloud } = getServices(); const [guidesState, setGuidesState] = useState([]); + const history = useHistory(); useEffect(() => { chrome.setBreadcrumbs([ @@ -76,6 +78,12 @@ export const GettingStarted = () => { fetchGuidesState(); }, [fetchGuidesState]); + useEffect(() => { + if (cloud?.isCloudEnabled === false) { + return history.push('/'); + } + }, [cloud, history]); + const onSkip = () => { trackUiMetric(METRIC_TYPE.CLICK, 'guided_onboarding__skipped'); // disable welcome screen on the home page @@ -92,6 +100,7 @@ export const GettingStarted = () => { await guidedOnboardingService?.activateGuide(useCase as GuideId, guideState); // TODO error handling https://github.com/elastic/kibana/issues/139798 }; + return ( @@ -109,7 +118,7 @@ export const GettingStarted = () => { {['search', 'observability', 'observabilityLink', 'security'].map((useCase) => { if (useCase === 'observabilityLink') { return ( - + { ); } return ( - + ({ getServices: () => ({ getBasePath: () => 'path', @@ -23,6 +24,7 @@ jest.mock('../kibana_services', () => ({ setBreadcrumbs: () => {}, }, application: { + navigateToUrl: mockNavigateToUrl, capabilities: { navLinks: { integrations: mockHasIntegrationsPermission, @@ -59,6 +61,7 @@ describe('home', () => { return `base_path/${url}`; }, hasUserDataView: jest.fn(async () => true), + isCloudEnabled: false, }; }); @@ -230,6 +233,16 @@ describe('home', () => { expect(component.find(Welcome).exists()).toBe(false); }); + + test('should redirect to guided onboarding on Cloud instead of welcome screen', async () => { + const isCloudEnabled = true; + const hasUserDataView = jest.fn(async () => false); + + const component = await renderHome({ isCloudEnabled, hasUserDataView }); + + expect(component.find(Welcome).exists()).toBe(false); + expect(mockNavigateToUrl).toHaveBeenCalledTimes(1); + }); }); describe('isNewKibanaInstance', () => { diff --git a/src/plugins/home/public/application/components/home.tsx b/src/plugins/home/public/application/components/home.tsx index f6b579213d420..707ea99ad8af4 100644 --- a/src/plugins/home/public/application/components/home.tsx +++ b/src/plugins/home/public/application/components/home.tsx @@ -33,6 +33,7 @@ export interface HomeProps { localStorage: Storage; urlBasePath: string; hasUserDataView: () => Promise; + isCloudEnabled: boolean; } interface State { @@ -126,7 +127,7 @@ export class Home extends Component { } private renderNormal() { - const { addBasePath, solutions } = this.props; + const { addBasePath, solutions, isCloudEnabled } = this.props; const { application, trackUiMetric } = getServices(); const isDarkMode = getServices().uiSettings?.get('theme:darkMode') || false; const devTools = this.findDirectoryById('console'); @@ -148,7 +149,12 @@ export class Home extends Component { > - + { public render() { const { isLoading, isWelcomeEnabled, isNewKibanaInstance } = this.state; + const { isCloudEnabled } = this.props; + const { application } = getServices(); if (isWelcomeEnabled) { if (isLoading) { return this.renderLoading(); } if (isNewKibanaInstance) { + if (isCloudEnabled) { + application.navigateToUrl('./home#/getting_started'); + return; + } return this.renderWelcome(); } } diff --git a/src/plugins/home/public/application/components/home_app.js b/src/plugins/home/public/application/components/home_app.js index af7b1dec48669..a6cdfec3b62e9 100644 --- a/src/plugins/home/public/application/components/home_app.js +++ b/src/plugins/home/public/application/components/home_app.js @@ -79,6 +79,7 @@ export function HomeApp({ directories, solutions }) { localStorage={localStorage} urlBasePath={getBasePath()} hasUserDataView={() => dataViewsService.hasUserDataView()} + isCloudEnabled={isCloudEnabled} /> diff --git a/src/plugins/home/public/application/components/sample_data/index.tsx b/src/plugins/home/public/application/components/sample_data/index.tsx index 8ce7a32b66e08..316ba615ce818 100644 --- a/src/plugins/home/public/application/components/sample_data/index.tsx +++ b/src/plugins/home/public/application/components/sample_data/index.tsx @@ -45,7 +45,7 @@ export function SampleDataCard({ urlBasePath, onDecline, onConfirm }: Props) { description={ } footer={ diff --git a/src/plugins/home/public/application/kibana_services.ts b/src/plugins/home/public/application/kibana_services.ts index b622cf862f315..af0a94b232fe6 100644 --- a/src/plugins/home/public/application/kibana_services.ts +++ b/src/plugins/home/public/application/kibana_services.ts @@ -21,6 +21,7 @@ import { UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; import { DataViewsContract } from '@kbn/data-views-plugin/public'; import { SharePluginSetup } from '@kbn/share-plugin/public'; import { GuidedOnboardingApi } from '@kbn/guided-onboarding-plugin/public'; +import { CloudSetup } from '@kbn/cloud-plugin/public'; import { TutorialService } from '../services/tutorials'; import { AddDataService } from '../services/add_data'; import { FeatureCatalogueRegistry } from '../services/feature_catalogue'; @@ -51,6 +52,7 @@ export interface HomeKibanaServices { addDataService: AddDataService; welcomeService: WelcomeService; guidedOnboardingService?: GuidedOnboardingApi; + cloud?: CloudSetup; } let services: HomeKibanaServices | null = null; diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts index c85f920fca6e1..b7270058aae6c 100644 --- a/src/plugins/home/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -105,6 +105,7 @@ export class HomePublicPlugin featureCatalogue: this.featuresCatalogueRegistry, welcomeService: this.welcomeService, guidedOnboardingService: guidedOnboarding.guidedOnboardingApi, + cloud, }); coreStart.chrome.docTitle.change( i18n.translate('home.pageTitle', { defaultMessage: 'Home' }) diff --git a/src/plugins/interactive_setup/server/elasticsearch_service.test.ts b/src/plugins/interactive_setup/server/elasticsearch_service.test.ts index 717c6bf56481a..bfc3f1b2daba5 100644 --- a/src/plugins/interactive_setup/server/elasticsearch_service.test.ts +++ b/src/plugins/interactive_setup/server/elasticsearch_service.test.ts @@ -86,7 +86,7 @@ describe('ElasticsearchService', () => { }); describe('#connectionStatus$', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); afterEach(() => jest.useRealTimers()); it('does not repeat ping request if have multiple subscriptions', async () => { diff --git a/src/plugins/kibana_utils/common/abort_utils.test.ts b/src/plugins/kibana_utils/common/abort_utils.test.ts index 0d34a7852fb44..a528f3e48476e 100644 --- a/src/plugins/kibana_utils/common/abort_utils.test.ts +++ b/src/plugins/kibana_utils/common/abort_utils.test.ts @@ -8,9 +8,10 @@ import { AbortError, abortSignalToPromise } from './abort_utils'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); -const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); +const flushPromises = () => + new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); describe('AbortUtils', () => { describe('AbortError', () => { diff --git a/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts b/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts index 20cc81bc3b75b..5148d1ad6c03f 100644 --- a/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts +++ b/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts @@ -109,7 +109,7 @@ describe('hashedItemStore', () => { beforeEach(() => { // Control time. - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); sessionStorage = new StubBrowserStorage(); hashedItemStore = new HashedItemStore(sessionStorage); @@ -199,7 +199,7 @@ describe('hashedItemStore', () => { beforeEach(() => { // Control time. - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); sessionStorage = new StubBrowserStorage(); hashedItemStore = new HashedItemStore(sessionStorage); @@ -350,7 +350,7 @@ describe('hashedItemStore', () => { beforeEach(() => { // Control time. - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); sessionStorage = new StubBrowserStorage(); hashedItemStore = new HashedItemStore(sessionStorage); }); diff --git a/src/plugins/newsfeed/public/plugin.test.ts b/src/plugins/newsfeed/public/plugin.test.ts index a082b81dc84be..2ab54026a4cfb 100644 --- a/src/plugins/newsfeed/public/plugin.test.ts +++ b/src/plugins/newsfeed/public/plugin.test.ts @@ -16,7 +16,7 @@ describe('Newsfeed plugin', () => { let plugin: NewsfeedPublicPlugin; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/src/plugins/telemetry/public/services/telemetry_sender.test.ts b/src/plugins/telemetry/public/services/telemetry_sender.test.ts index 7f986af97331d..b68ab85f0340f 100644 --- a/src/plugins/telemetry/public/services/telemetry_sender.test.ts +++ b/src/plugins/telemetry/public/services/telemetry_sender.test.ts @@ -246,7 +246,7 @@ describe('TelemetrySender', () => { beforeEach(() => { window.fetch = mockFetch = jest.fn(); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); consoleWarnMock = jest.spyOn(global.console, 'warn').mockImplementation(() => {}); }); @@ -380,7 +380,7 @@ describe('TelemetrySender', () => { }); describe('getRetryDelay', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); it('sets a minimum retry delay of 60 seconds', () => { @@ -399,7 +399,7 @@ describe('TelemetrySender', () => { }); describe('startChecking', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); it('calls sendIfDue every 60000 ms', () => { diff --git a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts index 3c87857591e69..b303f8b4c3a19 100644 --- a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts +++ b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts @@ -44,7 +44,7 @@ const reset = () => { executeFn.mockReset(); openContextMenuSpy.mockReset(); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }; beforeEach(reset); diff --git a/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx b/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx index c70f1df820252..07d35b78b58a2 100755 --- a/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx +++ b/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx @@ -75,7 +75,7 @@ export interface FieldStatsProps { 'data-test-subj'?: string; overrideMissingContent?: (params: { element: JSX.Element; - noDataFound?: boolean; + reason: 'no-data' | 'unsupported'; }) => JSX.Element | null; overrideFooter?: (params: { element: JSX.Element; @@ -304,7 +304,7 @@ const FieldStatsComponent: React.FC = ({ return overrideMissingContent ? overrideMissingContent({ - noDataFound: false, + reason: 'unsupported', element: messageNoAnalysis, }) : messageNoAnalysis; @@ -338,7 +338,7 @@ const FieldStatsComponent: React.FC = ({ return overrideMissingContent ? overrideMissingContent({ - noDataFound: true, + reason: 'no-data', element: messageNoData, }) : messageNoData; @@ -358,12 +358,14 @@ const FieldStatsComponent: React.FC = ({ defaultMessage: 'Top values', }), id: 'topValues', + 'data-test-subj': `${dataTestSubject}-buttonGroup-topValuesButton`, }, { label: i18n.translate('unifiedFieldList.fieldStats.fieldDistributionLabel', { defaultMessage: 'Distribution', }), id: 'histogram', + 'data-test-subj': `${dataTestSubject}-buttonGroup-distributionButton`, }, ]} onChange={(optionId: string) => { diff --git a/src/plugins/unified_search/public/dataview_picker/assets/adhoc.svg b/src/plugins/unified_search/public/dataview_picker/assets/adhoc.svg new file mode 100644 index 0000000000000..04cf8bc8f22ba --- /dev/null +++ b/src/plugins/unified_search/public/dataview_picker/assets/adhoc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx index 1e71da3a0b20a..3b987b8b19175 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -7,7 +7,7 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useState, useEffect, useCallback, useRef } from 'react'; +import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { css } from '@emotion/react'; import { EuiPopover, @@ -26,13 +26,13 @@ import { EuiToolTip, EuiSpacer, } from '@elastic/eui'; -import type { DataViewListItem } from '@kbn/data-views-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { IUnifiedSearchPluginServices } from '../types'; import type { DataViewPickerPropsExtended } from '.'; -import { DataViewsList } from './dataview_list'; +import { type DataViewListItemEnhanced, DataViewsList } from './dataview_list'; import type { TextBasedLanguagesListProps } from './text_languages_list'; import type { TextBasedLanguagesTransitionModalProps } from './text_languages_transition_modal'; +import adhoc from './assets/adhoc.svg'; import { changeDataViewStyles } from './change_dataview.styles'; // local storage key for the text based languages transition modal @@ -80,7 +80,7 @@ export function ChangeDataView({ const [noDataViewMatches, setNoDataViewMatches] = useState(false); const [dataViewSearchString, setDataViewSearchString] = useState(''); const [indexMatches, setIndexMatches] = useState(0); - const [dataViewsList, setDataViewsList] = useState([]); + const [dataViewsList, setDataViewsList] = useState([]); const [triggerLabel, setTriggerLabel] = useState(''); const [isTextBasedLangSelected, setIsTextBasedLangSelected] = useState( Boolean(textBasedLanguage) @@ -100,7 +100,7 @@ export function ChangeDataView({ useEffect(() => { const fetchDataViews = async () => { - const dataViewsRefs = await data.dataViews.getIdsWithTitle(); + const dataViewsRefs: DataViewListItemEnhanced[] = await data.dataViews.getIdsWithTitle(); if (adHocDataViews?.length) { adHocDataViews.forEach((adHocDataView) => { if (adHocDataView.id) { @@ -108,6 +108,7 @@ export function ChangeDataView({ title: adHocDataView.title, name: adHocDataView.name, id: adHocDataView.id, + isAdhoc: true, }); } }); @@ -151,6 +152,10 @@ export function ChangeDataView({ } }, [isTextBasedLangSelected, textBasedLanguage]); + const isAdHocSelected = useMemo(() => { + return adHocDataViews?.some((dataView) => dataView.id === currentDataViewId); + }, [adHocDataViews, currentDataViewId]); + const createTrigger = function () { const { label, title, 'data-test-subj': dataTestSubj, fullWidth, ...rest } = trigger; return ( @@ -168,7 +173,18 @@ export function ChangeDataView({ disabled={isDisabled} {...rest} > - {triggerLabel} + <> + {isAdHocSelected && ( + + )} + {triggerLabel} + ); }; diff --git a/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx b/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx index 1a4a1ddd4355e..108441954b53a 100644 --- a/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx +++ b/src/plugins/unified_search/public/dataview_picker/dataview_list.tsx @@ -7,13 +7,17 @@ */ import React from 'react'; -import { EuiSelectable, EuiSelectableProps, EuiPanel } from '@elastic/eui'; +import { EuiSelectable, EuiSelectableProps, EuiPanel, EuiBadge } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; import { DataViewListItem } from '@kbn/data-views-plugin/public'; +export interface DataViewListItemEnhanced extends DataViewListItem { + isAdhoc?: boolean; +} + export interface DataViewsListProps { - dataViewsList: DataViewListItem[]; + dataViewsList: DataViewListItemEnhanced[]; onChangeDataView: (newId: string) => void; isTextBasedLangSelected?: boolean; currentDataViewId?: string; @@ -40,11 +44,18 @@ export function DataViewsList({ data-test-subj="indexPattern-switcher" searchable singleSelection="always" - options={dataViewsList?.map(({ title, id, name }) => ({ + options={dataViewsList?.map(({ title, id, name, isAdhoc }) => ({ key: id, label: name ? name : title, value: id, checked: id === currentDataViewId && !Boolean(isTextBasedLangSelected) ? 'on' : undefined, + append: isAdhoc ? ( + + {i18n.translate('unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel', { + defaultMessage: 'Temporary', + })} + + ) : null, }))} onChange={(choices) => { const choice = choices.find(({ checked }) => checked) as unknown as { diff --git a/src/plugins/unified_search/public/dataview_picker/text_languages_list.tsx b/src/plugins/unified_search/public/dataview_picker/text_languages_list.tsx index 7b6ca242237c7..cf070b7746bfe 100644 --- a/src/plugins/unified_search/public/dataview_picker/text_languages_list.tsx +++ b/src/plugins/unified_search/public/dataview_picker/text_languages_list.tsx @@ -7,8 +7,8 @@ */ import React from 'react'; -import { EuiSelectable, EuiPanel, EuiBetaBadge } from '@elastic/eui'; -import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import { EuiSelectable, EuiPanel, EuiBadge } from '@elastic/eui'; import { TextBasedLanguages } from '.'; export interface TextBasedLanguagesListProps { @@ -39,15 +39,11 @@ export default function TextBasedLanguagesList({ value: lang, checked: lang === selectedOption ? 'on' : undefined, append: ( - + + {i18n.translate('unifiedSearch.query.queryBar.textBasedLanguagesTechPreviewLabel', { + defaultMessage: 'Technical preview', + })} + ), }))} onChange={(choices) => { diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx index 26b5c1770ee8a..8426275f10dfd 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -436,6 +436,7 @@ export const QueryBarTopRow = React.memo( size={shouldShowDatePickerAsBadge() ? 's' : 'm'} color={props.isDirty ? 'success' : 'primary'} fill={props.isDirty} + needsUpdate={props.isDirty} data-test-subj="querySubmitButton" // @ts-expect-error Need to fix expecting `children` in EUI toolTipProps={{ diff --git a/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx b/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx index dbf7af6afe12d..f6f433de9fcf1 100644 --- a/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_string_input.test.tsx @@ -28,7 +28,7 @@ import { QueryLanguageSwitcher } from './language_switcher'; import QueryStringInput from './query_string_input'; import { unifiedSearchPluginMock } from '../mocks'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); const startMock = coreMock.createStart(); const noop = () => { diff --git a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx index 7857704ba329d..88140ef3d46b3 100644 --- a/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx +++ b/src/plugins/unified_search/public/query_string_input/text_based_languages_editor/index.tsx @@ -438,7 +438,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ { + const layerId = 'layer-id'; + let vis: Vis; + + const metric: AvgColumn = { + sourceField: 'price', + columnId: 'column-1', + operationType: 'average', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + }; + const xColumn: DateHistogramColumn = { + sourceField: 'price', + columnId: 'column-2', + operationType: 'date_histogram', + isBucketed: true, + isSplit: false, + dataType: 'string', + params: { + interval: '1h', + }, + }; + + const yColumn: DateHistogramColumn = { + sourceField: 'price', + columnId: 'column-3', + operationType: 'date_histogram', + isBucketed: true, + isSplit: true, + dataType: 'string', + params: { + interval: '1h', + }, + }; + + beforeEach(() => { + vis = sampleHeatmapVis as unknown as Vis; + }); + + test('should return valid configuration', async () => { + const result = await getConfiguration(layerId, vis, { + metrics: [metric.columnId], + buckets: [xColumn.columnId, yColumn.columnId], + }); + expect(result).toEqual({ + gridConfig: { + isCellLabelVisible: true, + isXAxisLabelVisible: true, + isXAxisTitleVisible: true, + isYAxisLabelVisible: true, + isYAxisTitleVisible: true, + type: 'heatmap_grid', + }, + layerId, + layerType: 'data', + legend: { isVisible: undefined, position: 'right', type: 'heatmap_legend' }, + palette: { + accessor: 'column-1', + name: 'custom', + params: { + colorStops: [ + { color: '#F7FBFF', stop: 0 }, + { color: '#DEEBF7', stop: 12.5 }, + { color: '#C3DBEE', stop: 25 }, + { color: '#9CC8E2', stop: 37.5 }, + { color: '#6DAED5', stop: 50 }, + { color: '#4391C6', stop: 62.5 }, + { color: '#2271B3', stop: 75 }, + { color: '#0D5097', stop: 87.5 }, + ], + continuity: 'none', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 100, + rangeMin: 0, + rangeType: 'number', + reverse: false, + stops: [ + { color: '#F7FBFF', stop: 12.5 }, + { color: '#DEEBF7', stop: 25 }, + { color: '#C3DBEE', stop: 37.5 }, + { color: '#9CC8E2', stop: 50 }, + { color: '#6DAED5', stop: 62.5 }, + { color: '#4391C6', stop: 75 }, + { color: '#2271B3', stop: 87.5 }, + { color: '#0D5097', stop: 100 }, + ], + }, + type: 'palette', + }, + shape: 'heatmap', + valueAccessor: metric.columnId, + xAccessor: xColumn.columnId, + yAccessor: yColumn.columnId, + }); + }); +}); diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.ts new file mode 100644 index 0000000000000..2e7a3f161514a --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 or the Server + * Side Public License, v 1. + */ + +import { HeatmapConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { HeatmapVisParams } from '../../types'; +import { getPaletteForHeatmap } from './palette'; + +export const getConfiguration = async ( + layerId: string, + vis: Vis, + { + metrics, + buckets, + }: { + metrics: string[]; + buckets: string[]; + } +): Promise => { + const [valueAccessor] = metrics; + const [xAccessor, yAccessor] = buckets; + + const { params, uiState } = vis; + const state = uiState.get('vis', {}) ?? {}; + + const palette = await getPaletteForHeatmap(params); + return { + layerId, + layerType: 'data', + shape: 'heatmap', + legend: { + type: 'heatmap_legend', + isVisible: state.legendOpen, + position: params.legendPosition, + }, + gridConfig: { + type: 'heatmap_grid', + isCellLabelVisible: params.valueAxes?.[0].labels.show ?? false, + isXAxisLabelVisible: true, + isYAxisLabelVisible: true, + isYAxisTitleVisible: true, + isXAxisTitleVisible: true, + }, + valueAccessor, + xAccessor, + yAccessor, + palette: palette ? { ...palette, accessor: valueAccessor } : undefined, + }; +}; diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/palette.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/palette.ts new file mode 100644 index 0000000000000..32187e184d4ef --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/palette.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 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 or the Server + * Side Public License, v 1. + */ + +import { Range } from '@kbn/expressions-plugin/common'; +import { convertToLensModule } from '@kbn/visualizations-plugin/public'; +import { HeatmapVisParams } from '../../types'; +import { getStopsWithColorsFromColorsNumber } from '../../utils/palette'; + +type HeatmapVisParamsWithRanges = Omit & { + colorsRange: Exclude; +}; + +const isHeatmapVisParamsWithRanges = ( + params: HeatmapVisParams | HeatmapVisParamsWithRanges +): params is HeatmapVisParamsWithRanges => { + return Boolean(params.setColorRange && params.colorsRange && params.colorsRange.length); +}; + +export const getPaletteForHeatmap = async (params: HeatmapVisParams) => { + const { getPalette, getPaletteFromStopsWithColors, getPercentageModeConfig } = + await convertToLensModule; + + if (isHeatmapVisParamsWithRanges(params)) { + const percentageModeConfig = getPercentageModeConfig(params, false); + return getPalette(params, percentageModeConfig, params.percentageMode); + } + + const { color, stop = [] } = getStopsWithColorsFromColorsNumber( + params.colorsNumber, + params.colorSchema, + params.invertColors, + true + ); + const colorsRange: Range[] = [{ from: stop[0], to: stop[stop.length - 1], type: 'range' }]; + const { colorSchema, invertColors, percentageMode } = params; + const percentageModeConfig = getPercentageModeConfig( + { + colorsRange, + colorSchema, + invertColors, + percentageMode, + }, + false + ); + + return getPaletteFromStopsWithColors({ color, stop: stop ?? [] }, percentageModeConfig, true); +}; diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.test.ts new file mode 100644 index 0000000000000..ef86b3829c248 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.test.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 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 or the Server + * Side Public License, v 1. + */ + +import { ColorSchemas } from '@kbn/charts-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { convertToLens } from '.'; +import { HeatmapVisParams } from '../types'; + +const mockGetColumnsFromVis = jest.fn(); +const mockGetConfiguration = jest.fn().mockReturnValue({}); +const mockGetDataViewByIndexPatternId = jest.fn(); +const mockConvertToFiltersColumn = jest.fn(); + +jest.mock('../services', () => ({ + getDataViewsStart: jest.fn(() => ({ get: () => ({}), getDefault: () => ({}) })), +})); + +jest.mock('@kbn/visualizations-plugin/public', () => ({ + convertToLensModule: Promise.resolve({ + getColumnsFromVis: jest.fn(() => mockGetColumnsFromVis()), + convertToFiltersColumn: jest.fn(() => mockConvertToFiltersColumn()), + }), + getDataViewByIndexPatternId: jest.fn(() => mockGetDataViewByIndexPatternId()), +})); + +jest.mock('./configurations', () => ({ + getConfiguration: jest.fn(() => mockGetConfiguration()), +})); + +const params: HeatmapVisParams = { + addTooltip: false, + addLegend: false, + enableHover: true, + legendPosition: 'bottom', + lastRangeIsRightOpen: false, + percentageMode: false, + valueAxes: [], + colorSchema: ColorSchemas.Blues, + invertColors: false, + colorsNumber: 4, + setColorRange: true, +}; + +const vis = { + isHierarchical: () => false, + type: {}, + params, + data: {}, +} as unknown as Vis; + +const timefilter = { + getAbsoluteTime: () => {}, +} as any; + +describe('convertToLens', () => { + beforeEach(() => { + mockGetDataViewByIndexPatternId.mockReturnValue({ id: 'index-pattern' }); + mockConvertToFiltersColumn.mockReturnValue({ columnId: 'column-id-1' }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null if timefilter is undefined', async () => { + const result = await convertToLens(vis); + expect(result).toBeNull(); + }); + + test('should return null if mockGetDataViewByIndexPatternId returns null', async () => { + mockGetDataViewByIndexPatternId.mockReturnValue(null); + const result = await convertToLens(vis, timefilter); + expect(mockGetDataViewByIndexPatternId).toBeCalledTimes(1); + expect(mockGetColumnsFromVis).toBeCalledTimes(0); + expect(result).toBeNull(); + }); + + test('should return null if getColumnsFromVis returns null', async () => { + mockGetColumnsFromVis.mockReturnValue(null); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if metrics count is more than 1', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1', '2'], + buckets: { all: [] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return empty filters for x-axis if no buckets are specified', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: [] }, + columns: [{ columnId: '1', dataType: 'number' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + { columnId: 'column-id-1' }, + ], + }, + ]); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toEqual( + expect.objectContaining({ + configuration: {}, + indexPatternIds: ['index-pattern'], + layers: [ + expect.objectContaining({ + columnOrder: [], + columns: [{ columnId: '1', dataType: 'number' }, { columnId: 'column-id-1' }], + indexPatternId: 'index-pattern', + }), + ], + type: 'lnsHeatmap', + }) + ); + }); + + test('should return correct state for valid vis', async () => { + const config = { + layerType: 'data', + }; + + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: ['2'] }, + columns: [{ columnId: '1', dataType: 'number' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }, + ]); + mockGetConfiguration.mockReturnValue(config); + + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(mockGetConfiguration).toBeCalledTimes(1); + expect(result?.type).toEqual('lnsHeatmap'); + expect(result?.layers.length).toEqual(1); + expect(result?.layers[0]).toEqual( + expect.objectContaining({ + columnOrder: [], + columns: [{ columnId: '1', dataType: 'number' }, { columnId: 'column-id-1' }], + indexPatternId: 'index-pattern', + }) + ); + expect(result?.configuration).toEqual(config); + }); +}); diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts new file mode 100644 index 0000000000000..546d497e80560 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 or the Server + * Side Public License, v 1. + */ + +import { Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common'; +import { + convertToLensModule, + getDataViewByIndexPatternId, +} from '@kbn/visualizations-plugin/public'; +import uuid from 'uuid'; +import { getDataViewsStart } from '../services'; +import { getConfiguration } from './configurations'; +import { ConvertHeatmapToLensVisualization } from './types'; + +export const isColumnWithMeta = (column: Column): column is ColumnWithMeta => { + if ((column as ColumnWithMeta).meta) { + return true; + } + return false; +}; + +export const excludeMetaFromColumn = (column: Column) => { + if (isColumnWithMeta(column)) { + const { meta, ...rest } = column; + return rest; + } + return column; +}; + +export const convertToLens: ConvertHeatmapToLensVisualization = async (vis, timefilter) => { + if (!timefilter) { + return null; + } + + const dataViews = getDataViewsStart(); + const dataView = await getDataViewByIndexPatternId(vis.data.indexPattern?.id, dataViews); + + if (!dataView) { + return null; + } + + const { getColumnsFromVis, convertToFiltersColumn } = await convertToLensModule; + const layers = getColumnsFromVis(vis, timefilter, dataView, { + buckets: ['segment'], + splits: ['group'], + unsupported: ['split_row', 'split_column'], + }); + + if (layers === null) { + return null; + } + + const [layerConfig] = layers; + + const xColumn = layerConfig.columns.find(({ isBucketed, isSplit }) => isBucketed && !isSplit); + const xAxisColumn = + xColumn ?? + convertToFiltersColumn(uuid(), { filters: [{ input: { language: 'lucene', query: '*' } }] })!; + + if (xColumn?.columnId !== xAxisColumn?.columnId) { + layerConfig.buckets.all.push(xAxisColumn.columnId); + layerConfig.columns.push(xAxisColumn); + } + const yColumn = layerConfig.columns.find(({ isBucketed, isSplit }) => isBucketed && isSplit); + + if (!layerConfig.buckets.all.length || layerConfig.metrics.length > 1) { + return null; + } + + const layerId = uuid(); + + const indexPatternId = dataView.id!; + const configuration = await getConfiguration(layerId, vis, { + metrics: layerConfig.metrics, + buckets: [xAxisColumn.columnId, yColumn?.columnId].filter((c): c is string => + Boolean(c) + ), + }); + + return { + type: 'lnsHeatmap', + layers: [ + { + indexPatternId, + layerId, + columns: layerConfig.columns.map(excludeMetaFromColumn), + columnOrder: [], + }, + ], + configuration, + indexPatternIds: [indexPatternId], + }; +}; diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/types.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/types.ts new file mode 100644 index 0000000000000..732b977dd7b59 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/types.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 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 or the Server + * Side Public License, v 1. + */ + +import { TimefilterContract } from '@kbn/data-plugin/public'; +import { NavigateToLensContext, HeatmapConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { HeatmapVisParams } from '../types'; + +export type ConvertHeatmapToLensVisualization = ( + vis: Vis, + timefilter?: TimefilterContract +) => Promise | null>; diff --git a/src/plugins/vis_types/heatmap/public/plugin.ts b/src/plugins/vis_types/heatmap/public/plugin.ts index 44357cceaa86b..ee7349145e7c6 100644 --- a/src/plugins/vis_types/heatmap/public/plugin.ts +++ b/src/plugins/vis_types/heatmap/public/plugin.ts @@ -6,14 +6,16 @@ * Side Public License, v 1. */ -import { CoreSetup } from '@kbn/core/public'; +import { CoreSetup, CoreStart } from '@kbn/core/public'; import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; import type { ChartsPluginSetup } from '@kbn/charts-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { LEGACY_HEATMAP_CHARTS_LIBRARY } from '../common'; import { heatmapVisType } from './vis_type'; +import { setDataViewsStart } from './services'; /** @internal */ export interface VisTypeHeatmapSetupDependencies { @@ -28,6 +30,11 @@ export interface VisTypeHeatmapPluginStartDependencies { fieldFormats: FieldFormatsStart; } +/** @internal */ +export interface VisTypeHeatmapStartDependencies { + dataViews: DataViewsPublicPluginStart; +} + export class VisTypeHeatmapPlugin { setup( core: CoreSetup, @@ -44,5 +51,7 @@ export class VisTypeHeatmapPlugin { return {}; } - start() {} + start(core: CoreStart, { dataViews }: VisTypeHeatmapStartDependencies) { + setDataViewsStart(dataViews); + } } diff --git a/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts b/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts index 6a33feb853221..89ede55b951ef 100644 --- a/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts @@ -5,7 +5,9 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -export const sampleAreaVis = { + +const mockUiStateGet = jest.fn().mockReturnValue(() => {}); +export const sampleHeatmapVis = { type: { name: 'heatmap', title: 'Heatmap', @@ -1788,5 +1790,10 @@ export const sampleAreaVis = { }, }, isHierarchical: () => false, - uiState: {}, + uiState: { + vis: { + legendOpen: false, + }, + get: mockUiStateGet, + }, }; diff --git a/src/plugins/vis_types/heatmap/public/services.ts b/src/plugins/vis_types/heatmap/public/services.ts new file mode 100644 index 0000000000000..736ad70d49419 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/services.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 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 or the Server + * Side Public License, v 1. + */ + +import { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; + +export const [getDataViewsStart, setDataViewsStart] = + createGetterSetter('dataViews'); diff --git a/src/plugins/vis_types/heatmap/public/to_ast.test.ts b/src/plugins/vis_types/heatmap/public/to_ast.test.ts index d1e312755cf49..07585d9f2332f 100644 --- a/src/plugins/vis_types/heatmap/public/to_ast.test.ts +++ b/src/plugins/vis_types/heatmap/public/to_ast.test.ts @@ -7,7 +7,7 @@ */ import { Vis } from '@kbn/visualizations-plugin/public'; -import { sampleAreaVis } from './sample_vis.test.mocks'; +import { sampleHeatmapVis } from './sample_vis.test.mocks'; import { buildExpression } from '@kbn/expressions-plugin/public'; import { toExpressionAst } from './to_ast'; @@ -33,7 +33,7 @@ describe('heatmap vis toExpressionAst function', () => { } as any; beforeEach(() => { - vis = sampleAreaVis as any; + vis = sampleHeatmapVis as any; }); it('should match basic snapshot', () => { diff --git a/src/plugins/vis_types/heatmap/public/utils/palette.ts b/src/plugins/vis_types/heatmap/public/utils/palette.ts index aa978a2954e90..29109a55fd1e7 100644 --- a/src/plugins/vis_types/heatmap/public/utils/palette.ts +++ b/src/plugins/vis_types/heatmap/public/utils/palette.ts @@ -27,13 +27,20 @@ const getColor = ( export const getStopsWithColorsFromColorsNumber = ( colorsNumber: number | '', colorSchema: ColorSchemas, - invertColors: boolean = false + invertColors: boolean = false, + includeZeroElement: boolean = false ) => { const colors = []; const stops = []; if (!colorsNumber) { return { color: [] }; } + + if (includeZeroElement) { + colors.push(TRANSPARENT); + stops.push(0); + } + const step = 100 / colorsNumber; for (let i = 0; i < colorsNumber; i++) { colors.push(getColor(i, colorsNumber, colorSchema, invertColors)); diff --git a/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx b/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx index e5a92ca03f5cc..336da6e2d8041 100644 --- a/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx +++ b/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx @@ -16,6 +16,7 @@ import { HeatmapTypeProps, HeatmapVisParams, AxisType, ScaleType } from '../type import { toExpressionAst } from '../to_ast'; import { getHeatmapOptions } from '../editor/components'; import { SplitTooltip } from './split_tooltip'; +import { convertToLens } from '../convert_to_lens'; export const getHeatmapVisTypeDefinition = ({ showElasticChartsOptions = false, @@ -154,4 +155,10 @@ export const getHeatmapVisTypeDefinition = ({ ], }, requiresSearch: true, + navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), + }; + }, }); diff --git a/src/plugins/vis_types/pie/kibana.json b/src/plugins/vis_types/pie/kibana.json index 4c5ee6b50579e..d9dca861e33be 100644 --- a/src/plugins/vis_types/pie/kibana.json +++ b/src/plugins/vis_types/pie/kibana.json @@ -1,14 +1,27 @@ { - "id": "visTypePie", - "version": "kibana", - "ui": true, - "server": true, - "requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection", "expressionPartitionVis", "dataViews"], - "requiredBundles": ["visDefaultEditor", "kibanaUtils"], - "extraPublicDirs": ["common/index"], - "owner": { - "name": "Vis Editors", - "githubTeam": "kibana-vis-editors" - }, - "description": "Contains the pie chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting." - } + "id": "visTypePie", + "version": "kibana", + "ui": true, + "server": true, + "requiredPlugins": [ + "charts", + "data", + "expressions", + "visualizations", + "usageCollection", + "expressionPartitionVis", + "dataViews" + ], + "requiredBundles": [ + "visDefaultEditor", + "kibanaUtils" + ], + "extraPublicDirs": [ + "common/index" + ], + "owner": { + "name": "Vis Editors", + "githubTeam": "kibana-vis-editors" + }, + "description": "Contains the pie chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting." +} \ No newline at end of file diff --git a/src/plugins/vis_types/table/public/convert_to_lens/index.ts b/src/plugins/vis_types/table/public/convert_to_lens/index.ts index e69faccbfd7ec..ed23d612cb68c 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/index.ts @@ -73,6 +73,7 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi return null; } const percentageColumn = getPercentageColumnFormulaColumn({ + visType: vis.type.name, agg: metricAgg as SchemaConfig, dataView, aggs: visSchemas.metric as Array>, diff --git a/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts b/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts index 5d6d0594e8e62..6be4eeca6bdc3 100644 --- a/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts +++ b/src/plugins/vis_types/table/public/utils/use/use_ui_state.test.ts @@ -95,7 +95,7 @@ describe('useUiState', () => { describe('updating uiState through callbacks', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); it('should update the uiState with new sort', async () => { diff --git a/src/plugins/vis_types/timeseries/public/application/components/series_editor.js b/src/plugins/vis_types/timeseries/public/application/components/series_editor.js index 7bd72b85edc1d..531075b36244b 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/series_editor.js +++ b/src/plugins/vis_types/timeseries/public/application/components/series_editor.js @@ -65,6 +65,10 @@ export class SeriesEditor extends Component { } }; + handleSeriesChange = (doc) => { + handleChange(this.props, doc); + }; + render() { const { limit, model, name, fields, colorPicker } = this.props; const list = model[name].filter((val, index) => index < (limit || Infinity)); @@ -89,7 +93,7 @@ export class SeriesEditor extends Component { disableDelete={model[name].length < 2} fields={fields} onAdd={() => handleAdd(this.props, newSeriesFn)} - onChange={(doc) => handleChange(this.props, doc)} + onChange={this.handleSeriesChange} onClone={() => this.handleClone(row)} onDelete={() => handleDelete(this.props, row)} model={row} diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/config.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/config.js index 48ba0e5403665..506ce0dbdf2a9 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/config.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/config.js @@ -35,7 +35,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { getDefaultQueryLanguage } from '../../lib/get_default_query_language'; import { checkIfNumericMetric } from '../../lib/check_if_numeric_metric'; import { QueryBarWrapper } from '../../query_bar_wrapper'; -import { DATA_FORMATTERS } from '../../../../../common/enums'; +import { DATA_FORMATTERS, BUCKET_TYPES } from '../../../../../common/enums'; import { isConfigurationFeatureEnabled } from '../../../../../common/check_ui_restrictions'; import { filterCannotBeAppliedErrorMessage } from '../../../../../common/errors'; import { tsvbEditorRowStyles } from '../../../styles/common.styles'; @@ -50,13 +50,20 @@ class TableSeriesConfigUi extends Component { } } + handleAggregateByChange = (selectedOptions) => { + this.props.onChange({ + aggregate_by: selectedOptions?.[0], + }); + }; + + handleSelectChange = createSelectHandler(this.props.onChange); + handleTextChange = createTextHandler(this.props.onChange); + changeModelFormatter = (formatter) => this.props.onChange({ formatter }); render() { const defaults = { offset_time: '', value_template: '{{value}}' }; const model = { ...defaults, ...this.props.model }; - const handleSelectChange = createSelectHandler(this.props.onChange); - const handleTextChange = createTextHandler(this.props.onChange); const htmlId = htmlIdGenerator(); const functionOptions = [ @@ -160,7 +167,7 @@ class TableSeriesConfigUi extends Component { fullWidth > - + @@ -222,11 +229,7 @@ class TableSeriesConfigUi extends Component { fields={this.props.fields} indexPattern={this.props.panel.index_pattern} value={model.aggregate_by} - onChange={(value) => - this.props.onChange({ - aggregate_by: value?.[0], - }) - } + onChange={this.handleAggregateByChange} fullWidth restrict={[ KBN_FIELD_TYPES.NUMBER, @@ -236,7 +239,7 @@ class TableSeriesConfigUi extends Component { KBN_FIELD_TYPES.STRING, ]} uiRestrictions={this.props.uiRestrictions} - type={'terms'} + type={BUCKET_TYPES.TERMS} /> @@ -251,9 +254,10 @@ class TableSeriesConfigUi extends Component { fullWidth > diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts index cbc899981717b..9943cfa627e23 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.test.ts @@ -6,10 +6,11 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { METRIC_TYPES } from '@kbn/data-plugin/public'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { TSVB_METRIC_TYPES } from '../../../common/enums'; -import { Metric } from '../../../common/types'; +import { Panel, Metric } from '../../../common/types'; import { convertToLens } from '.'; import { createPanel, createSeries } from '../lib/__mocks__'; import { AvgColumn } from '../lib/convert'; @@ -58,6 +59,10 @@ describe('convertToLens', () => { series: [createSeries({ metrics: [metric] })], }); + const vis = { + params: model, + } as Vis; + const metricColumn: AvgColumn = { columnId: 'col-id', dataType: 'number', @@ -89,51 +94,55 @@ describe('convertToLens', () => { test('should return null for invalid metrics', async () => { mockIsValidMetrics.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockIsValidMetrics).toBeCalledTimes(1); }); test('should return null for invalid or unsupported metrics', async () => { mockGetMetricsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetMetricsColumns).toBeCalledTimes(1); }); test('should return null for invalid or unsupported buckets', async () => { mockGetBucketsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetBucketsColumns).toBeCalledTimes(1); }); test('should return null if metric is staticValue', async () => { const result = await convertToLens({ - ...model, - series: [ - { - ...model.series[0], - metrics: [...model.series[0].metrics, { type: TSVB_METRIC_TYPES.STATIC } as Metric], - }, - ], - }); + params: { + ...model, + series: [ + { + ...model.series[0], + metrics: [...model.series[0].metrics, { type: TSVB_METRIC_TYPES.STATIC } as Metric], + }, + ], + }, + } as Vis); expect(result).toBeNull(); expect(mockGetDataSourceInfo).toBeCalledTimes(0); }); test('should return null if only series agg is specified', async () => { const result = await convertToLens({ - ...model, - series: [ - { - ...model.series[0], - metrics: [ - { type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'min', id: 'some-id' } as Metric, - ], - }, - ], - }); + params: { + ...model, + series: [ + { + ...model.series[0], + metrics: [ + { type: TSVB_METRIC_TYPES.SERIES_AGG, function: 'min', id: 'some-id' } as Metric, + ], + }, + ], + }, + } as Vis); expect(result).toBeNull(); }); @@ -142,7 +151,7 @@ describe('convertToLens', () => { mockGetSeriesAgg.mockReturnValue({ metrics: [metric] }); mockGetConfigurationForGauge.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); }); @@ -151,8 +160,8 @@ describe('convertToLens', () => { mockGetSeriesAgg.mockReturnValue({ metrics: [metric] }); mockGetConfigurationForGauge.mockReturnValue({}); - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], @@ -163,8 +172,8 @@ describe('convertToLens', () => { hidden: false, }), ], - }) - ); + }), + } as Vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsMetric'); }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts index b97f8d59e9537..87d5333d4be51 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/gauge/index.ts @@ -45,7 +45,10 @@ const getMaxFormula = (metric: Metric, column?: Column) => { }))`; }; -export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeRange) => { +export const convertToLens: ConvertTsvbToLensVisualization = async ( + { params: model }, + timeRange +) => { const dataViews = getDataViewsStart(); const series = model.series[0]; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts index 309f066b18f29..a97395f64c113 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import type { Panel } from '../../common/types'; import { convertTSVBtoLensConfiguration } from '.'; @@ -42,7 +43,9 @@ describe('convertTSVBtoLensConfiguration', () => { ...model, type: 'markdown', } as Panel; - const triggerOptions = await convertTSVBtoLensConfiguration(metricModel); + const triggerOptions = await convertTSVBtoLensConfiguration({ + params: metricModel, + } as Vis); expect(triggerOptions).toBeNull(); }); @@ -51,7 +54,9 @@ describe('convertTSVBtoLensConfiguration', () => { ...model, use_kibana_indexes: false, }; - const triggerOptions = await convertTSVBtoLensConfiguration(stringIndexPatternModel); + const triggerOptions = await convertTSVBtoLensConfiguration({ + params: stringIndexPatternModel, + } as Vis); expect(triggerOptions).toBeNull(); }); }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts index a3d08e89e91a2..3e1982aa0903e 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/index.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { TimeRange } from '@kbn/data-plugin/common'; import type { Panel } from '../../common/types'; import { PANEL_TYPES } from '../../common/enums'; @@ -29,6 +30,10 @@ const getConvertFnByType = (type: PANEL_TYPES) => { const { convertToLens } = await import('./gauge'); return convertToLens; }, + [PANEL_TYPES.TABLE]: async () => { + const { convertToLens } = await import('./table'); + return convertToLens; + }, }; return convertionFns[type]?.(); @@ -39,17 +44,17 @@ const getConvertFnByType = (type: PANEL_TYPES) => { * Returns the Lens model, only if it is supported. If not, it returns null. * In case of null, the menu item is disabled and the user can't navigate to Lens. */ -export const convertTSVBtoLensConfiguration = async (model: Panel, timeRange?: TimeRange) => { +export const convertTSVBtoLensConfiguration = async (vis: Vis, timeRange?: TimeRange) => { // Disables the option for not supported charts, for the string mode and for series with annotations - if (!model.use_kibana_indexes) { + if (!vis.params.use_kibana_indexes) { return null; } // Disables if model is invalid - if (model.isModelInvalid) { + if (vis.params.isModelInvalid) { return null; } - const convertFn = await getConvertFnByType(model.type); + const convertFn = await getConvertFnByType(vis.params.type); - return (await convertFn?.(model, timeRange)) ?? null; + return (await convertFn?.(vis, timeRange)) ?? null; }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts index 9cb0238f9d265..0fabef6eebdbe 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.test.ts @@ -14,7 +14,7 @@ import { getConfigurationForMetric, getConfigurationForGauge } from '.'; const mockGetPalette = jest.fn(); -jest.mock('./palette', () => ({ +jest.mock('../palette', () => ({ getPalette: jest.fn(() => mockGetPalette()), })); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts index 7b49d604b2343..e6814b0797a1a 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/index.ts @@ -10,7 +10,7 @@ import color from 'color'; import { MetricVisConfiguration } from '@kbn/visualizations-plugin/common'; import { Panel } from '../../../../../common/types'; import { Column, Layer } from '../../convert'; -import { getPalette } from './palette'; +import { getPalette } from '../palette'; import { findMetricColumn, getMetricWithCollapseFn } from '../../../utils'; export const getConfigurationForMetric = ( diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.test.ts similarity index 99% rename from src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts rename to src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.test.ts index b7356f094f91a..059f0372c451a 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { getPalette } from './palette'; +import { getPalette } from '.'; describe('getPalette', () => { const baseColor = '#fff'; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.ts similarity index 83% rename from src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts rename to src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.ts index 4079ffb396647..159804a1ea29b 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/metric/palette.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/palette/index.ts @@ -8,7 +8,7 @@ import color from 'color'; import { ColorStop, CustomPaletteParams, PaletteOutput } from '@kbn/coloring'; import { uniqBy } from 'lodash'; -import { Panel } from '../../../../../common/types'; +import { Panel, Series } from '../../../../../common/types'; const Operators = { GTE: 'gte', @@ -24,9 +24,11 @@ type ColorStopsWithMinMax = Pick< type MetricColorRules = Exclude; type GaugeColorRules = Exclude; +type SeriesColorRules = Exclude; type MetricColorRule = MetricColorRules[number]; type GaugeColorRule = GaugeColorRules[number]; +type SeriesColorRule = SeriesColorRules[number]; type ValidMetricColorRule = Omit & ( @@ -44,31 +46,47 @@ type ValidGaugeColorRule = Omit & { gauge: Exclude; }; +type ValidSeriesColorRule = Omit & { + text: Exclude; +}; + const isValidColorRule = ( rule: MetricColorRule | GaugeColorRule -): rule is ValidMetricColorRule | ValidGaugeColorRule => { +): rule is ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule => { const { background_color: bColor, color: textColor } = rule as MetricColorRule; const { gauge } = rule as GaugeColorRule; + const { text } = rule as SeriesColorRule; - return rule.operator && (bColor ?? textColor ?? gauge) && rule.value !== undefined ? true : false; + return Boolean( + rule.operator && (bColor ?? textColor ?? gauge ?? text) && rule.value !== undefined + ); }; const isMetricColorRule = ( - rule: ValidMetricColorRule | ValidGaugeColorRule + rule: ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule ): rule is ValidMetricColorRule => { const metricRule = rule as ValidMetricColorRule; return metricRule.background_color ?? metricRule.color ? true : false; }; -const getColor = (rule: ValidMetricColorRule | ValidGaugeColorRule) => { +const isGaugeColorRule = ( + rule: ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule +): rule is ValidGaugeColorRule => { + const metricRule = rule as ValidGaugeColorRule; + return Boolean(metricRule.gauge); +}; + +const getColor = (rule: ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule) => { if (isMetricColorRule(rule)) { return rule.background_color ?? rule.color; + } else if (isGaugeColorRule(rule)) { + return rule.gauge; } - return rule.gauge; + return rule.text; }; const getColorStopsWithMinMaxForAllGteOrWithLte = ( - rules: Array, + rules: Array, tailOperator: string, baseColor?: string ): ColorStopsWithMinMax => { @@ -125,7 +143,7 @@ const getColorStopsWithMinMaxForAllGteOrWithLte = ( }; const getColorStopsWithMinMaxForLtWithLte = ( - rules: Array + rules: Array ): ColorStopsWithMinMax => { const lastRule = rules[rules.length - 1]; const colorStops = rules.reduce((colors, rule, index, rulesArr) => { @@ -166,7 +184,7 @@ const getColorStopsWithMinMaxForLtWithLte = ( }; const getColorStopWithMinMaxForLte = ( - rule: ValidMetricColorRule | ValidGaugeColorRule + rule: ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule ): ColorStopsWithMinMax => { const colorStop = { color: color(getColor(rule)).hex(), @@ -183,7 +201,7 @@ const getColorStopWithMinMaxForLte = ( }; const getColorStopWithMinMaxForGte = ( - rule: ValidMetricColorRule | ValidGaugeColorRule, + rule: ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule, baseColor?: string ): ColorStopsWithMinMax => { const colorStop = { @@ -224,12 +242,14 @@ const getCustomPalette = ( }; export const getPalette = ( - rules: MetricColorRules | GaugeColorRules, + rules: MetricColorRules | GaugeColorRules | SeriesColorRules, baseColor?: string ): PaletteOutput | null | undefined => { - const validRules = (rules as Array).filter< - ValidMetricColorRule | ValidGaugeColorRule - >((rule): rule is ValidMetricColorRule | ValidGaugeColorRule => isValidColorRule(rule)); + const validRules = (rules as Array).filter< + ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule + >((rule): rule is ValidMetricColorRule | ValidGaugeColorRule | ValidSeriesColorRule => + isValidColorRule(rule) + ); validRules.sort((rule1, rule2) => { return rule1.value! - rule2.value!; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.test.ts new file mode 100644 index 0000000000000..6deeaaa39fb8f --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 or the Server + * Side Public License, v 1. + */ + +import { createSeries } from '../../__mocks__'; +import { getColumnState } from '.'; + +const mockGetPalette = jest.fn(); + +jest.mock('../palette', () => ({ + getPalette: jest.fn(() => mockGetPalette()), +})); + +describe('getColumnState', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockGetPalette.mockReturnValue({ id: 'custom' }); + }); + + test('should return column state without palette if series is not provided', () => { + const config = getColumnState('test'); + expect(config).toEqual({ + columnId: 'test', + alignment: 'left', + colorMode: 'none', + }); + expect(mockGetPalette).toBeCalledTimes(0); + }); + + test('should return column state with palette if series is provided', () => { + const config = getColumnState('test', undefined, createSeries()); + expect(config).toEqual({ + columnId: 'test', + alignment: 'left', + colorMode: 'text', + palette: { id: 'custom' }, + }); + expect(mockGetPalette).toBeCalledTimes(1); + }); + + test('should return column state with collapseFn if collapseFn is provided', () => { + const config = getColumnState('test', 'max', createSeries()); + expect(config).toEqual({ + columnId: 'test', + alignment: 'left', + colorMode: 'text', + palette: { id: 'custom' }, + collapseFn: 'max', + }); + expect(mockGetPalette).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.ts new file mode 100644 index 0000000000000..7f152fa218842 --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/table/index.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 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 or the Server + * Side Public License, v 1. + */ + +import { Series } from '../../../../../common/types'; +import { getPalette } from '../palette'; + +export const getColumnState = (columnId: string, collapseFn?: string, series?: Series) => { + const palette = series ? getPalette(series.color_rules ?? []) : undefined; + return { + columnId, + alignment: 'left' as const, + colorMode: palette ? 'text' : 'none', + ...(palette ? { palette } : {}), + ...(collapseFn ? { collapseFn } : {}), + }; +}; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts index c06cc3e722279..2be4e09bf8898 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/column.ts @@ -32,7 +32,7 @@ interface ExtraColumnFields { const isSupportedFormat = (format: string) => ['bytes', 'number', 'percent'].includes(format); -export const getFormat = (series: Series): FormatParams => { +export const getFormat = (series: Pick): FormatParams => { let suffix; if (!series.formatter || series.formatter === 'default') { diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/date_histogram.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/date_histogram.ts index f2173cf56b469..0887ad1168ddf 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/date_histogram.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/date_histogram.ts @@ -9,28 +9,36 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import uuid from 'uuid'; import { DateHistogramParams, DataType } from '@kbn/visualizations-plugin/common/convert_to_lens'; -import { DateHistogramColumn } from './types'; -import type { Panel, Series } from '../../../../common/types'; +import { DateHistogramColumn, DateHistogramSeries } from './types'; +import type { Panel } from '../../../../common/types'; const getInterval = (interval?: string) => { return interval && !interval?.includes('=') ? interval : 'auto'; }; -export const convertToDateHistogramParams = (model: Panel, series: Series): DateHistogramParams => { +export const convertToDateHistogramParams = ( + model: Panel | undefined, + series: DateHistogramSeries, + includeEmptyRows: boolean = true +): DateHistogramParams => { return { - interval: getInterval(series.override_index_pattern ? series.series_interval : model.interval), + interval: getInterval(series.override_index_pattern ? series.series_interval : model?.interval), dropPartials: series.override_index_pattern ? series.series_drop_last_bucket > 0 - : model.drop_last_bucket > 0, - includeEmptyRows: true, + : (model?.drop_last_bucket ?? 0) > 0, + includeEmptyRows, }; }; export const convertToDateHistogramColumn = ( - model: Panel, - series: Series, + model: Panel | undefined, + series: DateHistogramSeries, dataView: DataView, - { fieldName, isSplit }: { fieldName: string; isSplit: boolean } + { + fieldName, + isSplit, + includeEmptyRows = true, + }: { fieldName: string; isSplit: boolean; includeEmptyRows?: boolean } ): DateHistogramColumn | null => { const dateField = dataView.getFieldByName(fieldName); @@ -38,7 +46,7 @@ export const convertToDateHistogramColumn = ( return null; } - const params = convertToDateHistogramParams(model, series); + const params = convertToDateHistogramParams(model, series, includeEmptyRows); return { columnId: uuid(), diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/filters.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/filters.ts index 9134504813b68..05d74337e848d 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/filters.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/filters.ts @@ -8,10 +8,9 @@ import uuid from 'uuid'; import { FiltersParams } from '@kbn/visualizations-plugin/common/convert_to_lens'; -import { FiltersColumn } from './types'; -import type { Series } from '../../../../common/types'; +import { FiltersColumn, FiltersSeries } from './types'; -export const convertToFiltersParams = (series: Series): FiltersParams => { +export const convertToFiltersParams = (series: FiltersSeries): FiltersParams => { const splitFilters = []; if (series.split_mode === 'filter' && series.filter) { splitFilters.push({ filter: series.filter }); @@ -35,7 +34,10 @@ export const convertToFiltersParams = (series: Series): FiltersParams => { }; }; -export const convertToFiltersColumn = (series: Series, isSplit: boolean): FiltersColumn | null => { +export const convertToFiltersColumn = ( + series: FiltersSeries, + isSplit: boolean +): FiltersColumn | null => { const params = convertToFiltersParams(series); if (!params.filters.length) { return null; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.test.ts index 907fe458c6a64..5d6dc036a7bd0 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.test.ts @@ -47,6 +47,11 @@ describe('convertToStaticValueColumn', () => { [{ series, metrics: [metric], dataView }, { visibleSeriesCount: 1 }], null, ], + [ + 'null if value is not specified', + [{ series, metrics: [metric], dataView }, { visibleSeriesCount: 2 }], + null, + ], [ 'static value column', [{ series, metrics: [{ ...metric, value: 'some value' }], dataView }], diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts index d3e6aef09b1cf..7990107bb5bf9 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/static_value.ts @@ -32,6 +32,9 @@ export const convertToStaticValueColumn = ( return null; } const currentMetric = metrics[metrics.length - 1]; + if (!currentMetric.value) { + return null; + } return { operationType: 'static_value', references: [], @@ -68,7 +71,10 @@ export const convertStaticValueToFormulaColumn = ( return null; } const currentMetric = metrics[metrics.length - 1]; - return createFormulaColumn(currentMetric.value ?? '', { + if (!currentMetric.value) { + return null; + } + return createFormulaColumn(currentMetric.value, { series, metric: currentMetric, dataView, diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/terms.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/terms.ts index 977de1947d4f8..c31d8dca68ced 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/terms.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/terms.ts @@ -9,16 +9,15 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { DataType, TermsParams } from '@kbn/visualizations-plugin/common'; import uuid from 'uuid'; -import { Series } from '../../../../common/types'; import { excludeMetaFromColumn, getFormat, isColumnWithMeta } from './column'; -import { Column, TermsColumn } from './types'; +import { Column, TermsColumn, TermsSeries } from './types'; interface OrderByWithAgg { orderAgg?: TermsParams['orderAgg']; orderBy: TermsParams['orderBy']; } -const getOrderByWithAgg = (series: Series, columns: Column[]): OrderByWithAgg | null => { +const getOrderByWithAgg = (series: TermsSeries, columns: Column[]): OrderByWithAgg | null => { if (series.terms_order_by === '_key') { return { orderBy: { type: 'alphabetical' } }; } @@ -56,7 +55,7 @@ const getOrderByWithAgg = (series: Series, columns: Column[]): OrderByWithAgg | }; export const convertToTermsParams = ( - series: Series, + series: TermsSeries, columns: Column[], secondaryFields: string[] ): TermsParams | null => { @@ -84,10 +83,11 @@ export const convertToTermsParams = ( export const convertToTermsColumn = ( termFields: [string, ...string[]], - series: Series, + series: TermsSeries, columns: Column[], dataView: DataView, - isSplit: boolean = false + isSplit: boolean = false, + label?: string ): TermsColumn | null => { const [baseField, ...secondaryFields] = termFields; const field = dataView.getFieldByName(baseField); @@ -108,6 +108,7 @@ export const convertToTermsColumn = ( sourceField: field.name, isBucketed: true, isSplit, + label, params: { ...params, ...getFormat(series) }, }; }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts index 4b3b6c582f915..943550aee0066 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/convert/types.ts @@ -117,4 +117,24 @@ export interface CommonColumnConverterArgs { dataView: DataView; } +export type TermsSeries = Pick< + Series, + | 'split_mode' + | 'terms_direction' + | 'terms_order_by' + | 'terms_size' + | 'terms_include' + | 'terms_exclude' + | 'terms_field' + | 'formatter' + | 'value_template' +>; + +export type FiltersSeries = Pick; + +export type DateHistogramSeries = Pick< + Series, + 'split_mode' | 'override_index_pattern' | 'series_interval' | 'series_drop_last_bucket' +>; + export { FiltersColumn, TermsColumn, DateHistogramColumn }; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts index debe064940c8e..a2130de36abd4 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/metrics/supported_metrics.ts @@ -68,6 +68,7 @@ const supportedPanelTypes: readonly PANEL_TYPES[] = [ PANEL_TYPES.TOP_N, PANEL_TYPES.METRIC, PANEL_TYPES.GAUGE, + PANEL_TYPES.TABLE, ]; const supportedTimeRangeModes: readonly TIME_RANGE_DATA_MODES[] = [ diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/buckets_columns.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/buckets_columns.ts index c0aa201de6837..8ca184d443a68 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/buckets_columns.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/series/buckets_columns.ts @@ -7,18 +7,21 @@ */ import type { DataView } from '@kbn/data-views-plugin/common'; -import { Series, Panel } from '../../../../common/types'; +import { Panel } from '../../../../common/types'; import { getFieldsForTerms } from '../../../../common/fields_utils'; import { Column, convertToFiltersColumn, convertToDateHistogramColumn, convertToTermsColumn, + TermsSeries, + FiltersSeries, + DateHistogramSeries, } from '../convert'; import { getValidColumns } from './columns'; export const isSplitWithDateHistogram = ( - series: Series, + series: TermsSeries, splitFields: string[], dataView: DataView ) => { @@ -39,27 +42,49 @@ export const isSplitWithDateHistogram = ( return false; }; +const isFiltersSeries = ( + series: DateHistogramSeries | TermsSeries | FiltersSeries +): series is FiltersSeries => { + return series.split_mode === 'filters' || series.split_mode === 'filter'; +}; + +const isTermsSeries = ( + series: DateHistogramSeries | TermsSeries | FiltersSeries +): series is TermsSeries => { + return series.split_mode === 'terms'; +}; + +const isDateHistogramSeries = ( + series: DateHistogramSeries | TermsSeries | FiltersSeries, + isDateHistogram: boolean +): series is DateHistogramSeries => { + return isDateHistogram && series.split_mode === 'terms'; +}; + export const getBucketsColumns = ( - model: Panel, - series: Series, + model: Panel | undefined, + series: DateHistogramSeries | TermsSeries | FiltersSeries, columns: Column[], dataView: DataView, - isSplit: boolean = false + isSplit: boolean = false, + label?: string, + includeEmptyRowsForDateHistogram: boolean = true ) => { - if (series.split_mode === 'filters' || series.split_mode === 'filter') { + if (isFiltersSeries(series)) { const filterColumn = convertToFiltersColumn(series, true); return getValidColumns([filterColumn]); } - if (series.split_mode === 'terms') { + if (isTermsSeries(series)) { const splitFields = getFieldsForTerms(series.terms_field); const isDateHistogram = isSplitWithDateHistogram(series, splitFields, dataView); if (isDateHistogram === null) { return null; } - if (isDateHistogram) { + if (isDateHistogramSeries(series, isDateHistogram)) { const dateHistogramColumn = convertToDateHistogramColumn(model, series, dataView, { fieldName: splitFields[0], isSplit: true, + includeEmptyRows: includeEmptyRowsForDateHistogram, }); return getValidColumns(dateHistogramColumn); } @@ -73,7 +98,8 @@ export const getBucketsColumns = ( series, columns, dataView, - isSplit + isSplit, + label ); return getValidColumns(termsColumn); } diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts index 9407599573d9d..6d62994be4447 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.test.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { METRIC_TYPES } from '@kbn/data-plugin/public'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { convertToLens } from '.'; import { createPanel, createSeries } from '../lib/__mocks__'; +import { Panel } from '../../../common/types'; const mockGetMetricsColumns = jest.fn(); const mockGetBucketsColumns = jest.fn(); @@ -54,6 +56,10 @@ describe('convertToLens', () => { ], }); + const vis = { + params: model, + } as Vis; + const bucket = { isBucketed: true, isSplit: true, @@ -135,27 +141,27 @@ describe('convertToLens', () => { test('should return null for invalid metrics', async () => { mockIsValidMetrics.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockIsValidMetrics).toBeCalledTimes(1); }); test('should return null for invalid or unsupported metrics', async () => { mockGetMetricsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetMetricsColumns).toBeCalledTimes(1); }); test('should return null for invalid or unsupported buckets', async () => { mockGetBucketsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetBucketsColumns).toBeCalledTimes(1); }); test('should return state for valid model', async () => { - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsMetric'); expect(mockGetBucketsColumns).toBeCalledTimes(model.series.length); @@ -163,16 +169,16 @@ describe('convertToLens', () => { }); test('should skip hidden series', async () => { - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], hidden: true, }), ], - }) - ); + }), + } as Vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsMetric'); expect(mockIsValidMetrics).toBeCalledTimes(0); @@ -185,8 +191,8 @@ describe('convertToLens', () => { indexPattern: { id: 'test-index-pattern-1' }, }); - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], @@ -197,8 +203,8 @@ describe('convertToLens', () => { hidden: false, }), ], - }) - ); + }), + } as Vis); expect(result).toBeNull(); }); @@ -207,8 +213,8 @@ describe('convertToLens', () => { mockGetBucketsColumns.mockReturnValueOnce([]); mockGetMetricsColumns.mockReturnValueOnce([metric]); - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], @@ -219,8 +225,8 @@ describe('convertToLens', () => { hidden: false, }), ], - }) - ); + }), + } as Vis); expect(result).toBeNull(); }); @@ -229,8 +235,8 @@ describe('convertToLens', () => { mockGetBucketsColumns.mockReturnValueOnce([bucket2]); mockGetMetricsColumns.mockReturnValueOnce([metric]); - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], @@ -241,8 +247,8 @@ describe('convertToLens', () => { hidden: false, }), ], - }) - ); + }), + } as Vis); expect(result).toBeNull(); }); @@ -251,8 +257,8 @@ describe('convertToLens', () => { mockGetBucketsColumns.mockReturnValueOnce([bucket]); mockGetMetricsColumns.mockReturnValueOnce([metric]); - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], @@ -263,8 +269,8 @@ describe('convertToLens', () => { hidden: false, }), ], - }) - ); + }), + } as Vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsMetric'); expect(mockGetConfigurationForMetric).toBeCalledTimes(1); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts index 149acc513b9ff..8577623b8bd93 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/metric/index.ts @@ -22,12 +22,16 @@ import { excludeMetaFromLayers, getUniqueBuckets } from '../utils'; const MAX_SERIES = 2; const MAX_BUCKETS = 2; -export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeRange) => { +export const convertToLens: ConvertTsvbToLensVisualization = async ( + { params: model }, + timeRange +) => { const dataViews = getDataViewsStart(); const seriesNum = model.series.filter((series) => !series.hidden).length; const indexPatternIds = new Set(); - const visibleSeries = model.series.filter(({ hidden }) => !hidden); + // we should get max only 2 series + const visibleSeries = model.series.filter(({ hidden }) => !hidden).slice(0, 2); let currentIndexPattern: DataView | null = null; for (const series of visibleSeries) { const datasourceInfo = await getDataSourceInfo( diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.test.ts new file mode 100644 index 0000000000000..37676b302fba1 --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.test.ts @@ -0,0 +1,235 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 or the Server + * Side Public License, v 1. + */ + +import { TableVisConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { METRIC_TYPES } from '@kbn/data-plugin/public'; +import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { convertToLens } from '.'; +import { createPanel, createSeries } from '../lib/__mocks__'; +import { Panel } from '../../../common/types'; +import { TSVB_METRIC_TYPES } from '../../../common/enums'; + +const mockConvertToDateHistogramColumn = jest.fn(); +const mockGetMetricsColumns = jest.fn(); +const mockGetBucketsColumns = jest.fn(); +const mockGetConfigurationForTimeseries = jest.fn(); +const mockIsValidMetrics = jest.fn(); +const mockGetDatasourceValue = jest + .fn() + .mockImplementation(() => Promise.resolve(stubLogstashDataView)); +const mockGetDataSourceInfo = jest.fn(); +const mockGetColumnState = jest.fn(); + +jest.mock('../../services', () => ({ + getDataViewsStart: jest.fn(() => mockGetDatasourceValue), +})); + +jest.mock('../lib/convert', () => ({ + excludeMetaFromColumn: jest.fn().mockReturnValue({}), +})); + +jest.mock('../lib/series', () => ({ + getMetricsColumns: jest.fn(() => mockGetMetricsColumns()), + getBucketsColumns: jest.fn(() => mockGetBucketsColumns()), +})); + +jest.mock('../lib/configurations/table', () => ({ + getColumnState: jest.fn(() => mockGetColumnState()), +})); + +jest.mock('../lib/metrics', () => ({ + isValidMetrics: jest.fn(() => mockIsValidMetrics()), + getReducedTimeRange: jest.fn().mockReturnValue('10'), +})); + +jest.mock('../lib/datasource', () => ({ + getDataSourceInfo: jest.fn(() => mockGetDataSourceInfo()), +})); + +describe('convertToLens', () => { + const model = createPanel({ + series: [ + createSeries({ + metrics: [ + { id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }, + { id: 'some-id-1', type: METRIC_TYPES.COUNT }, + ], + }), + ], + }); + + const vis = { + params: model, + uiState: { + get: () => ({}), + }, + } as Vis; + + beforeEach(() => { + mockIsValidMetrics.mockReturnValue(true); + mockGetDataSourceInfo.mockReturnValue({ + indexPatternId: 'test-index-pattern', + timeField: 'timeField', + indexPattern: { id: 'test-index-pattern' }, + }); + mockConvertToDateHistogramColumn.mockReturnValue({}); + mockGetMetricsColumns.mockReturnValue([{}]); + mockGetBucketsColumns.mockReturnValue([{}]); + mockGetConfigurationForTimeseries.mockReturnValue({ layers: [] }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null for invalid metrics', async () => { + mockIsValidMetrics.mockReturnValue(null); + const result = await convertToLens(vis); + expect(result).toBeNull(); + expect(mockIsValidMetrics).toBeCalledTimes(1); + }); + + test('should return null for invalid or unsupported metrics', async () => { + mockGetMetricsColumns.mockReturnValue(null); + const result = await convertToLens(vis); + expect(result).toBeNull(); + expect(mockGetMetricsColumns).toBeCalledTimes(1); + }); + + test('should return null if several series have different “Field” + “Aggregate function”', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [createSeries({ aggregate_by: 'new' }), createSeries({ aggregate_by: 'test' })], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeNull(); + expect(mockGetBucketsColumns).toBeCalledTimes(1); + }); + + test('should return null if “Aggregate function” is not supported', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [createSeries({ aggregate_by: 'new', aggregate_function: 'cumulative_sum' })], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeNull(); + expect(mockGetBucketsColumns).toBeCalledTimes(1); + }); + + test('should return null if model have not visible metrics', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: true, + }), + ], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeNull(); + }); + + test('should return null if only static value is visible metric', async () => { + mockGetMetricsColumns.mockReturnValue([ + { columnId: 'metric-column-1', operationType: 'static_value' }, + ]); + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: TSVB_METRIC_TYPES.STATIC }], + hidden: true, + }), + ], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeNull(); + }); + + test('should return state for valid model', async () => { + const result = await convertToLens(vis); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsDatatable'); + expect(mockGetBucketsColumns).toBeCalledTimes(1); + // every series + group by + expect(mockGetColumnState).toBeCalledTimes(model.series.length + 1); + }); + + test('should return state for valid model with “Field” + “Aggregate function”', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [createSeries({ aggregate_by: 'new', aggregate_function: 'sum' })], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsDatatable'); + expect(mockGetBucketsColumns).toBeCalledTimes(2); + // every series + group by + (“Field” + “Aggregate function”) + expect(mockGetColumnState).toBeCalledTimes(model.series.length + 2); + }); + + test('should return correct sorting config', async () => { + mockGetMetricsColumns.mockReturnValue([{ columnId: 'metric-column-1' }]); + const result = await convertToLens({ + params: createPanel({ + series: [createSeries({ id: 'test' })], + }), + uiState: { + get: () => ({ sort: { order: 'decs', column: 'test' } }), + }, + } as Vis); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsDatatable'); + expect((result?.configuration as TableVisConfiguration).sorting).toEqual({ + direction: 'decs', + columnId: 'metric-column-1', + }); + expect(mockGetBucketsColumns).toBeCalledTimes(1); + // every series + group by + expect(mockGetColumnState).toBeCalledTimes(model.series.length + 1); + }); + + test('should skip hidden series', async () => { + const result = await convertToLens({ + params: createPanel({ + series: [ + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + hidden: true, + }), + createSeries({ + metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], + }), + ], + }), + uiState: { + get: () => ({}), + }, + } as Vis); + expect(result).toBeDefined(); + expect(result?.type).toBe('lnsDatatable'); + expect(mockIsValidMetrics).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.ts new file mode 100644 index 0000000000000..0219d1080724b --- /dev/null +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/table/index.ts @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 or the Server + * Side Public License, v 1. + */ + +import uuid from 'uuid'; +import { parseTimeShift } from '@kbn/data-plugin/common'; +import { getIndexPatternIds, Layer } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { PANEL_TYPES } from '../../../common/enums'; +import { getDataViewsStart } from '../../services'; +import { getColumnState } from '../lib/configurations/table'; +import { getDataSourceInfo } from '../lib/datasource'; +import { getMetricsColumns, getBucketsColumns } from '../lib/series'; +import { getReducedTimeRange, isValidMetrics } from '../lib/metrics'; +import { ConvertTsvbToLensVisualization } from '../types'; +import { Layer as ExtendedLayer, excludeMetaFromColumn, Column } from '../lib/convert'; + +const excludeMetaFromLayers = (layers: Record): Record => { + const newLayers: Record = {}; + Object.entries(layers).forEach(([layerId, layer]) => { + const columns = layer.columns.map(excludeMetaFromColumn); + newLayers[layerId] = { ...layer, columns }; + }); + + return newLayers; +}; + +export const convertToLens: ConvertTsvbToLensVisualization = async ( + { params: model, uiState }, + timeRange +) => { + const columnStates = []; + const dataViews = getDataViewsStart(); + const seriesNum = model.series.filter((series) => !series.hidden).length; + const sortConfig = uiState.get('table')?.sort ?? {}; + + const datasourceInfo = await getDataSourceInfo( + model.index_pattern, + model.time_field, + false, + undefined, + undefined, + dataViews + ); + + if (!datasourceInfo) { + return null; + } + + const { indexPatternId, indexPattern } = datasourceInfo; + + const commonBucketsColumns = getBucketsColumns( + undefined, + { + split_mode: 'terms', + terms_field: model.pivot_id, + terms_size: model.pivot_rows ? model.pivot_rows.toString() : undefined, + }, + [], + indexPattern!, + false, + model.pivot_label, + false + ); + + if (!commonBucketsColumns) { + return null; + } + + const sortConfiguration = { + columnId: commonBucketsColumns[0].columnId, + direction: sortConfig.order, + }; + + columnStates.push(getColumnState(commonBucketsColumns[0].columnId)); + + let bucketsColumns: Column[] | null = []; + + if ( + !model.series.every( + (s) => + ((!s.aggregate_by && !model.series[0].aggregate_by) || + s.aggregate_by === model.series[0].aggregate_by) && + ((!s.aggregate_function && !model.series[0].aggregate_function) || + s.aggregate_function === model.series[0].aggregate_function) + ) + ) { + return null; + } + + if (model.series[0].aggregate_by) { + if ( + !model.series[0].aggregate_function || + !['sum', 'mean', 'min', 'max'].includes(model.series[0].aggregate_function) + ) { + return null; + } + bucketsColumns = getBucketsColumns( + undefined, + { + split_mode: 'terms', + terms_field: model.series[0].aggregate_by, + }, + [], + indexPattern!, + false + ); + if (bucketsColumns === null) { + return null; + } + + columnStates.push( + getColumnState( + bucketsColumns[0].columnId, + model.series[0].aggregate_function === 'mean' ? 'avg' : model.series[0].aggregate_function + ) + ); + } + + const metrics = []; + + // handle multiple layers/series + for (const [_, series] of model.series.entries()) { + if (series.hidden) { + continue; + } + + // not valid time shift + if (series.offset_time && parseTimeShift(series.offset_time) === 'invalid') { + return null; + } + + if (!isValidMetrics(series.metrics, PANEL_TYPES.TABLE, series.time_range_mode)) { + return null; + } + + const reducedTimeRange = getReducedTimeRange(model, series, timeRange); + + // handle multiple metrics + const metricsColumns = getMetricsColumns(series, indexPattern!, seriesNum, { + reducedTimeRange, + }); + if (!metricsColumns) { + return null; + } + + columnStates.push(getColumnState(metricsColumns[0].columnId, undefined, series)); + + if (sortConfig.column === series.id) { + sortConfiguration.columnId = metricsColumns[0].columnId; + } + + metrics.push(...metricsColumns); + } + + if (!metrics.length || metrics.every((metric) => metric.operationType === 'static_value')) { + return null; + } + + const extendedLayer: ExtendedLayer = { + indexPatternId: indexPatternId as string, + layerId: uuid(), + columns: [...metrics, ...commonBucketsColumns, ...bucketsColumns], + columnOrder: [], + }; + + const layers = Object.values(excludeMetaFromLayers({ 0: extendedLayer })); + + return { + type: 'lnsDatatable', + layers, + configuration: { + columns: columnStates, + layerId: extendedLayer.layerId, + layerType: 'data', + sorting: sortConfiguration, + }, + indexPatternIds: getIndexPatternIds(layers), + }; +}; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts index c81db38e05384..64fee3484b3b6 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { METRIC_TYPES } from '@kbn/data-plugin/public'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { convertToLens } from '.'; import { createPanel, createSeries } from '../lib/__mocks__'; +import { Panel } from '../../../common/types'; const mockConvertToDateHistogramColumn = jest.fn(); const mockGetMetricsColumns = jest.fn(); @@ -60,6 +62,10 @@ describe('convertToLens', () => { ], }); + const vis = { + params: model, + } as Vis; + beforeEach(() => { mockIsValidMetrics.mockReturnValue(true); mockGetDataSourceInfo.mockReturnValue({ @@ -79,35 +85,35 @@ describe('convertToLens', () => { test('should return null for invalid metrics', async () => { mockIsValidMetrics.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockIsValidMetrics).toBeCalledTimes(1); }); test('should return null for empty time field', async () => { mockGetDataSourceInfo.mockReturnValue({ timeField: null }); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetDataSourceInfo).toBeCalledTimes(1); }); test('should return null for invalid date histogram', async () => { mockConvertToDateHistogramColumn.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockConvertToDateHistogramColumn).toBeCalledTimes(1); }); test('should return null for invalid or unsupported metrics', async () => { mockGetMetricsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetMetricsColumns).toBeCalledTimes(1); }); test('should return null for invalid or unsupported buckets', async () => { mockGetBucketsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetBucketsColumns).toBeCalledTimes(1); }); @@ -119,14 +125,14 @@ describe('convertToLens', () => { operationType: 'static_value', }, ]); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetMetricsColumns).toBeCalledTimes(1); expect(mockGetBucketsColumns).toBeCalledTimes(1); }); test('should return state for valid model', async () => { - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsXY'); expect(mockGetBucketsColumns).toBeCalledTimes(model.series.length); @@ -134,16 +140,16 @@ describe('convertToLens', () => { }); test('should skip hidden series', async () => { - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], hidden: true, }), ], - }) - ); + }), + } as Vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsXY'); expect(mockIsValidMetrics).toBeCalledTimes(0); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts index ef678fcc2dab4..a08b7113c4a7b 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts @@ -14,7 +14,6 @@ import { } from '@kbn/visualizations-plugin/common/convert_to_lens'; import uuid from 'uuid'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { Panel } from '../../../common/types'; import { PANEL_TYPES } from '../../../common/enums'; import { getDataViewsStart } from '../../services'; import { getDataSourceInfo } from '../lib/datasource'; @@ -41,7 +40,7 @@ const excludeMetaFromLayers = (layers: Record): Record { +export const convertToLens: ConvertTsvbToLensVisualization = async ({ params: model }) => { const dataViews: DataViewsPublicPluginStart = getDataViewsStart(); const extendedLayers: Record = {}; const seriesNum = model.series.filter((series) => !series.hidden).length; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts index 7e4776f10ac9f..646323a6691d5 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.test.ts @@ -6,10 +6,12 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { METRIC_TYPES } from '@kbn/data-plugin/public'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { convertToLens } from '.'; import { createPanel, createSeries } from '../lib/__mocks__'; +import { Panel } from '../../../common/types'; const mockGetMetricsColumns = jest.fn(); const mockGetBucketsColumns = jest.fn(); @@ -59,6 +61,10 @@ describe('convertToLens', () => { ], }); + const vis = { + params: model, + } as Vis; + beforeEach(() => { mockIsValidMetrics.mockReturnValue(true); mockGetDataSourceInfo.mockReturnValue({ @@ -77,27 +83,27 @@ describe('convertToLens', () => { test('should return null for invalid metrics', async () => { mockIsValidMetrics.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockIsValidMetrics).toBeCalledTimes(1); }); test('should return null for invalid or unsupported metrics', async () => { mockGetMetricsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetMetricsColumns).toBeCalledTimes(1); }); test('should return null for invalid or unsupported buckets', async () => { mockGetBucketsColumns.mockReturnValue(null); - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeNull(); expect(mockGetBucketsColumns).toBeCalledTimes(1); }); test('should return state for valid model', async () => { - const result = await convertToLens(model); + const result = await convertToLens(vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsXY'); expect(mockGetBucketsColumns).toBeCalledTimes(model.series.length); @@ -105,16 +111,16 @@ describe('convertToLens', () => { }); test('should skip hidden series', async () => { - const result = await convertToLens( - createPanel({ + const result = await convertToLens({ + params: createPanel({ series: [ createSeries({ metrics: [{ id: 'some-id', type: METRIC_TYPES.AVG, field: 'test-field' }], hidden: true, }), ], - }) - ); + }), + } as Vis); expect(result).toBeDefined(); expect(result?.type).toBe('lnsXY'); expect(mockIsValidMetrics).toBeCalledTimes(0); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts index 130646f72f127..9505b7f5f0c7e 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts @@ -28,7 +28,10 @@ const excludeMetaFromLayers = (layers: Record): Record { +export const convertToLens: ConvertTsvbToLensVisualization = async ( + { params: model }, + timeRange +) => { const dataViews = getDataViewsStart(); const extendedLayers: Record = {}; const seriesNum = model.series.filter((series) => !series.hidden).length; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts index 69a90c7864eb3..9f00a669ea5c3 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/types.ts @@ -6,18 +6,22 @@ * Side Public License, v 1. */ +import { Vis } from '@kbn/visualizations-plugin/public'; import { MetricVisConfiguration, NavigateToLensContext, XYConfiguration, + TableVisConfiguration, } from '@kbn/visualizations-plugin/common'; import { TimeRange } from '@kbn/data-plugin/common'; import type { Panel } from '../../common/types'; export type ConvertTsvbToLensVisualization = ( - model: Panel, + vis: Vis, timeRange?: TimeRange -) => Promise | null>; +) => Promise | null>; export interface Filter { kql?: string | { [key: string]: any } | undefined; diff --git a/src/plugins/vis_types/timeseries/public/metrics_type.ts b/src/plugins/vis_types/timeseries/public/metrics_type.ts index 3bf9f9b90bf1a..43be3ee3004f4 100644 --- a/src/plugins/vis_types/timeseries/public/metrics_type.ts +++ b/src/plugins/vis_types/timeseries/public/metrics_type.ts @@ -171,15 +171,13 @@ export const metricsVisDefinition: VisTypeDefinition< return { canNavigateToLens: Boolean( vis?.params - ? await convertTSVBtoLensConfiguration(vis.params as Panel, timeFilter?.getAbsoluteTime()) + ? await convertTSVBtoLensConfiguration(vis, timeFilter?.getAbsoluteTime()) : null ), }; }, navigateToLens: async (vis, timeFilter) => - vis?.params - ? await convertTSVBtoLensConfiguration(vis?.params as Panel, timeFilter?.getAbsoluteTime()) - : null, + vis?.params ? await convertTSVBtoLensConfiguration(vis, timeFilter?.getAbsoluteTime()) : null, inspectorAdapters: () => ({ requests: new RequestAdapter(), diff --git a/src/plugins/vis_types/vislib/public/vis_controller.tsx b/src/plugins/vis_types/vislib/public/vis_controller.tsx index af9dda7ccf01f..40a518a8c0c9b 100644 --- a/src/plugins/vis_types/vislib/public/vis_controller.tsx +++ b/src/plugins/vis_types/vislib/public/vis_controller.tsx @@ -9,13 +9,10 @@ import $ from 'jquery'; import React, { RefObject } from 'react'; -import { METRIC_TYPE } from '@kbn/analytics'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; import type { PersistedState } from '@kbn/visualizations-plugin/public'; import { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public'; -import { KibanaExecutionContext } from '@kbn/core-execution-context-common'; -import { getUsageCollectionStart } from './services'; import { VisTypeVislibCoreSetup } from './plugin'; import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend'; import { BasicVislibParams } from './types'; @@ -30,38 +27,6 @@ const legendClassName = { export type VislibVisController = InstanceType>; -/** @internal **/ -const extractContainerType = (context?: KibanaExecutionContext): string | undefined => { - if (context) { - const recursiveGet = (item: KibanaExecutionContext): KibanaExecutionContext | undefined => { - if (item.type) { - return item; - } else if (item.child) { - return recursiveGet(item.child); - } - }; - return recursiveGet(context)?.type; - } -}; - -const renderComplete = ( - visParams: BasicVislibParams | PieVisParams, - handlers: IInterpreterRenderHandlers -) => { - const usageCollection = getUsageCollectionStart(); - const containerType = extractContainerType(handlers.getExecutionContext()); - - if (usageCollection && containerType) { - usageCollection.reportUiCounter( - containerType, - METRIC_TYPE.COUNT, - `render_agg_based_${visParams.type}` - ); - } - - handlers.done(); -}; - export const createVislibVisController = ( core: VisTypeVislibCoreSetup, charts: ChartsPluginSetup @@ -99,7 +64,8 @@ export const createVislibVisController = ( async render( esResponse: any, visParams: BasicVislibParams | PieVisParams, - handlers: IInterpreterRenderHandlers + handlers: IInterpreterRenderHandlers, + renderComplete: (() => void) | undefined ): Promise { if (this.vislibVis) { this.destroy(false); @@ -109,7 +75,7 @@ export const createVislibVisController = ( this.chartEl.dataset.vislibChartType = visParams.type; if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { - renderComplete(visParams, handlers); + renderComplete?.(); return; } @@ -133,7 +99,7 @@ export const createVislibVisController = ( this.mountLegend(esResponse, visParams, fireEvent, uiState as PersistedState); } - renderComplete(visParams, handlers); + renderComplete?.(); }); this.removeListeners = () => { diff --git a/src/plugins/vis_types/vislib/public/vis_wrapper.tsx b/src/plugins/vis_types/vislib/public/vis_wrapper.tsx index a800abd3d11fb..5a68e74b5a485 100644 --- a/src/plugins/vis_types/vislib/public/vis_wrapper.tsx +++ b/src/plugins/vis_types/vislib/public/vis_wrapper.tsx @@ -6,20 +6,23 @@ * Side Public License, v 1. */ -import React, { useEffect, useMemo, useRef } from 'react'; -import { EuiResizeObserver } from '@elastic/eui'; +import React, { useEffect, useMemo, useRef, useCallback } from 'react'; +import { EuiResizeObserver, EuiResizeObserverProps } from '@elastic/eui'; import { debounce } from 'lodash'; import { IInterpreterRenderHandlers } from '@kbn/expressions-plugin/public'; import type { PersistedState } from '@kbn/visualizations-plugin/public'; import { ChartsPluginSetup } from '@kbn/charts-plugin/public'; +import { KibanaExecutionContext } from '@kbn/core-execution-context-common'; +import { METRIC_TYPE } from '@kbn/analytics'; import { VislibRenderValue } from './vis_type_vislib_vis_fn'; import { createVislibVisController, VislibVisController } from './vis_controller'; import { VisTypeVislibCoreSetup } from './plugin'; import { PieRenderValue } from './pie_fn'; import './index.scss'; +import { getUsageCollectionStart } from './services'; type VislibWrapperProps = (VislibRenderValue | PieRenderValue) & { core: VisTypeVislibCoreSetup; @@ -27,20 +30,67 @@ type VislibWrapperProps = (VislibRenderValue | PieRenderValue) & { handlers: IInterpreterRenderHandlers; }; +/** @internal **/ +const extractContainerType = (context?: KibanaExecutionContext): string | undefined => { + if (context) { + const recursiveGet = (item: KibanaExecutionContext): KibanaExecutionContext | undefined => { + if (item.type) { + return item; + } else if (item.child) { + return recursiveGet(item.child); + } + }; + return recursiveGet(context)?.type; + } +}; + const VislibWrapper = ({ core, charts, visData, visConfig, handlers }: VislibWrapperProps) => { const chartDiv = useRef(null); const visController = useRef(null); + const skipRenderComplete = useRef(true); + + const renderComplete = useMemo( + () => () => { + const usageCollection = getUsageCollectionStart(); + const containerType = extractContainerType(handlers.getExecutionContext()); + + if (usageCollection && containerType) { + usageCollection.reportUiCounter( + containerType, + METRIC_TYPE.COUNT, + `render_agg_based_${visConfig!.type}` + ); + } + handlers.done(); + }, + [handlers, visConfig] + ); - const updateChart = useMemo( + const renderChart = useMemo( () => debounce(() => { if (visController.current) { - visController.current.render(visData, visConfig, handlers); + visController.current.render( + visData, + visConfig, + handlers, + skipRenderComplete.current ? undefined : renderComplete + ); } + skipRenderComplete.current = true; }, 100), - [visConfig, visData, handlers] + [handlers, renderComplete, skipRenderComplete, visConfig, visData] ); + const onResize: EuiResizeObserverProps['onResize'] = useCallback(() => { + renderChart(); + }, [renderChart]); + + useEffect(() => { + skipRenderComplete.current = false; + renderChart(); + }, [renderChart]); + useEffect(() => { if (chartDiv.current) { const Controller = createVislibVisController(core, charts); @@ -52,22 +102,20 @@ const VislibWrapper = ({ core, charts, visData, visConfig, handlers }: VislibWra }; }, [core, charts]); - useEffect(updateChart, [updateChart]); - useEffect(() => { if (handlers.uiState) { const uiState = handlers.uiState as PersistedState; - uiState.on('change', updateChart); + uiState.on('change', renderChart); return () => { - uiState?.off('change', updateChart); + uiState?.off('change', renderChart); }; } - }, [handlers.uiState, updateChart]); + }, [handlers.uiState, renderChart]); return ( - + {(resizeRef) => (
    diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts index f0a8e4d32f7c3..02a6140625c07 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts @@ -8,7 +8,7 @@ import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { BUCKET_TYPES, METRIC_TYPES } from '@kbn/data-plugin/common'; -import { convertBucketToColumns } from '.'; +import { BucketAggs, convertBucketToColumns } from '.'; import { DateHistogramColumn, FiltersColumn, RangeColumn, TermsColumn } from '../../types'; import { AggBasedColumn, SchemaConfig } from '../../..'; @@ -27,7 +27,7 @@ jest.mock('../convert', () => ({ describe('convertBucketToColumns', () => { const field = stubLogstashDataView.fields[0].name; const dateField = stubLogstashDataView.fields.find((f) => f.type === 'date')!.name; - const bucketAggs: SchemaConfig[] = [ + const bucketAggs: Array> = [ { accessor: 0, label: '', @@ -152,6 +152,7 @@ describe('convertBucketToColumns', () => { }, }, ]; + const visType = 'heatmap'; afterEach(() => { jest.clearAllMocks(); @@ -167,7 +168,7 @@ describe('convertBucketToColumns', () => { >([ [ 'null if bucket agg type is not supported', - [{ dataView: stubLogstashDataView, agg: bucketAggs[6], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[6], aggs, metricColumns, visType }], () => {}, null, ], @@ -179,6 +180,7 @@ describe('convertBucketToColumns', () => { agg: { ...bucketAggs[0], aggParams: undefined }, aggs, metricColumns, + visType, }, ], () => {}, @@ -186,7 +188,7 @@ describe('convertBucketToColumns', () => { ], [ 'filters column if bucket agg is valid filters agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[0], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[0], aggs, metricColumns, visType }], () => { mockConvertToFiltersColumn.mockReturnValue({ operationType: 'filters', @@ -198,7 +200,7 @@ describe('convertBucketToColumns', () => { ], [ 'date histogram column if bucket agg is valid date histogram agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[1], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[1], aggs, metricColumns, visType }], () => { mockConvertToDateHistogramColumn.mockReturnValue({ operationType: 'date_histogram', @@ -210,7 +212,7 @@ describe('convertBucketToColumns', () => { ], [ 'date histogram column if bucket agg is valid terms agg with date field', - [{ dataView: stubLogstashDataView, agg: bucketAggs[3], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[3], aggs, metricColumns, visType }], () => { mockConvertToDateHistogramColumn.mockReturnValue({ operationType: 'date_histogram', @@ -222,7 +224,7 @@ describe('convertBucketToColumns', () => { ], [ 'terms column if bucket agg is valid terms agg with no date field', - [{ dataView: stubLogstashDataView, agg: bucketAggs[2], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[2], aggs, metricColumns, visType }], () => { mockConvertToTermsColumn.mockReturnValue({ operationType: 'terms', @@ -234,7 +236,7 @@ describe('convertBucketToColumns', () => { ], [ 'range column if bucket agg is valid histogram agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[4], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[4], aggs, metricColumns, visType }], () => { mockConvertToRangeColumn.mockReturnValue({ operationType: 'range', @@ -246,7 +248,7 @@ describe('convertBucketToColumns', () => { ], [ 'range column if bucket agg is valid range agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[5], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[5], aggs, metricColumns, visType }], () => { mockConvertToRangeColumn.mockReturnValue({ operationType: 'range', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts index 0f929189f3369..db02b1e09fdce 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts @@ -9,9 +9,8 @@ import { BUCKET_TYPES, IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; import type { DataView } from '@kbn/data-views-plugin/common'; import { convertToSchemaConfig } from '../../../vis_schemas'; -import { SchemaConfig } from '../../..'; +import { AggBasedColumn, SchemaConfig } from '../../..'; import { - AggBasedColumn, CommonBucketConverterArgs, convertToDateHistogramColumn, convertToFiltersColumn, @@ -26,6 +25,7 @@ export type BucketAggs = | BUCKET_TYPES.FILTERS | BUCKET_TYPES.RANGE | BUCKET_TYPES.HISTOGRAM; + const SUPPORTED_BUCKETS: string[] = [ BUCKET_TYPES.TERMS, BUCKET_TYPES.DATE_HISTOGRAM, @@ -39,7 +39,7 @@ const isSupportedBucketAgg = (agg: SchemaConfig): agg is SchemaConfig, + { agg, dataView, metricColumns, aggs, visType }: CommonBucketConverterArgs, { label, isSplit = false, @@ -76,7 +76,7 @@ export const getBucketColumns = ( if (field.type !== 'date') { return convertToTermsColumn( agg.aggId ?? '', - { agg, dataView, metricColumns, aggs }, + { agg, dataView, metricColumns, aggs, visType }, label, isSplit ); @@ -102,7 +102,9 @@ export const convertBucketToColumns = ( dataView, metricColumns, aggs, + visType, }: { + visType: string; agg: SchemaConfig | IAggConfig; dataView: DataView; metricColumns: AggBasedColumn[]; @@ -116,7 +118,7 @@ export const convertBucketToColumns = ( return null; } return getBucketColumns( - { agg: currentAgg, dataView, metricColumns, aggs }, + { agg: currentAgg, dataView, metricColumns, aggs, visType }, { label: getLabel(currentAgg), isSplit, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts index c4592f50836c5..b4934d0bb0c85 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export { getPalette } from './palette'; +export { getPalette, getPaletteFromStopsWithColors } from './palette'; export { getPercentageModeConfig } from './percentage_mode'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts index a89177c914996..3f81291fab201 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts @@ -74,6 +74,21 @@ const convertToPercentColorStops = ( return { ...colorStops, stop }; }; +export const getPaletteFromStopsWithColors = ( + config: PaletteConfig, + percentageModeConfig: PercentageModeConfig, + isPercentPaletteSupported: boolean = false +) => { + const percentStopsWithColors = percentageModeConfig.isPercentageMode + ? convertToPercentColorStops(config, percentageModeConfig, isPercentPaletteSupported) + : config; + + return buildCustomPalette( + buildPaletteParams(percentStopsWithColors), + isPercentPaletteSupported && percentageModeConfig.isPercentageMode + ); +}; + export const getPalette = ( params: PaletteParams, percentageModeConfig: PercentageModeConfig, @@ -86,12 +101,10 @@ export const getPalette = ( } const stopsWithColors = getStopsWithColorsFromRanges(colorsRange, colorSchema, invertColors); - const percentStopsWithColors = percentageModeConfig.isPercentageMode - ? convertToPercentColorStops(stopsWithColors, percentageModeConfig, isPercentPaletteSupported) - : stopsWithColors; - return buildCustomPalette( - buildPaletteParams(percentStopsWithColors), - isPercentPaletteSupported && percentageModeConfig.isPercentageMode + return getPaletteFromStopsWithColors( + stopsWithColors, + percentageModeConfig, + isPercentPaletteSupported ); }; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts index 0ad2a4072e19d..e79be2ba51516 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts @@ -21,6 +21,7 @@ export const createFormulaColumn = (formula: string, agg: SchemaConfig): Formula operationType: 'formula', ...createColumn(agg), references: [], + dataType: 'number', params: { ...params, ...getFormat() }, timeShift: agg.aggParams?.timeShift, meta: { aggId: createAggregationId(agg) }, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts index 55ba1e8b5e09d..c46055ca6a9ab 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts @@ -22,6 +22,7 @@ jest.mock('../utils', () => ({ })); describe('convertToLastValueColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const sortField = dataView.fields[0]; @@ -59,7 +60,13 @@ describe('convertToLastValueColumn', () => { test.each<[string, Parameters, Partial | null]>([ [ 'null if top hits size is more than 1', - [{ agg: { ...topHitAgg, aggParams: { ...topHitAgg.aggParams!, size: 2 } }, dataView }], + [ + { + agg: { ...topHitAgg, aggParams: { ...topHitAgg.aggParams!, size: 2 } }, + dataView, + visType, + }, + ], null, ], [ @@ -74,6 +81,7 @@ describe('convertToLastValueColumn', () => { }, }, dataView, + visType, }, ], null, @@ -88,7 +96,7 @@ describe('convertToLastValueColumn', () => { test('should skip if top hit field is not specified', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toBeNull(); + expect(convertToLastValueColumn({ agg: topHitAgg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -97,14 +105,14 @@ describe('convertToLastValueColumn', () => { mockGetFieldByName.mockReturnValue(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toBeNull(); + expect(convertToLastValueColumn({ agg: topHitAgg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); expect(mockGetLabel).toBeCalledTimes(0); }); test('should return top hit column if top hit field is not present in index pattern', () => { - expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toEqual( + expect(convertToLastValueColumn({ agg: topHitAgg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someLabel', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts index 3162cf14e71c3..9525f4b41b7eb 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts @@ -25,7 +25,11 @@ const convertToLastValueParams = ( }; export const convertToLastValueColumn = ( - { agg, dataView }: CommonColumnConverterArgs, + { + visType, + agg, + dataView, + }: CommonColumnConverterArgs, reducedTimeRange?: string ): LastValueColumn | null => { const { aggParams } = agg; @@ -43,7 +47,7 @@ export const convertToLastValueColumn = ( } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts index 3be17abc46ac1..a0419d46df6b5 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts @@ -16,6 +16,7 @@ const mockGetFieldByName = jest.fn(); describe('convertToLastValueColumn', () => { const dataView = stubLogstashDataView; + const visType = 'heatmap'; const agg: SchemaConfig = { accessor: 0, @@ -42,6 +43,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.TOP_HITS], { agg, dataView, + visType, }) ).toBeNull(); }); @@ -54,6 +56,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.AVG], { agg, dataView, + visType, }) ).toBeNull(); expect(dataView.getFieldByName).toBeCalledTimes(1); @@ -67,6 +70,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.COUNT], { agg, dataView, + visType, }) ).toEqual(expect.objectContaining({ operationType: 'count' })); expect(dataView.getFieldByName).toBeCalledTimes(1); @@ -80,6 +84,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.AVG], { agg, dataView, + visType, }) ).toEqual( expect.objectContaining({ diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts index eb21b9f0fe91d..dd6c8b02687b0 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts @@ -78,7 +78,7 @@ export const isMetricWithField = ( export const convertMetricAggregationColumnWithoutSpecialParams = ( aggregation: SupportedMetric, - { agg, dataView }: CommonColumnConverterArgs, + { visType, agg, dataView }: CommonColumnConverterArgs, reducedTimeRange?: string ): MetricAggregationColumnWithoutSpecialParams | null => { if (!isSupportedAggregationWithoutParams(aggregation.name)) { @@ -94,7 +94,7 @@ export const convertMetricAggregationColumnWithoutSpecialParams = ( } const field = dataView.getFieldByName(sourceField); - if (!isFieldValid(field, aggregation)) { + if (!isFieldValid(visType, field, aggregation)) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts index c28324533c837..65dd1cf40aaef 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts @@ -40,6 +40,7 @@ jest.mock('../metrics', () => ({ })); describe('convertToOtherParentPipelineAggColumns', () => { + const visType = 'heatmap'; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ { @@ -81,6 +82,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -95,6 +97,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -112,6 +115,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -129,6 +133,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -147,6 +152,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -170,6 +176,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -188,6 +195,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -229,6 +237,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { }); describe('convertToCumulativeSumAggColumn', () => { + const visType = 'heatmap'; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ { @@ -280,6 +289,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: { ...aggs[1], aggParams: undefined } as SchemaConfig, + visType, }, ], () => { @@ -294,6 +304,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -308,6 +319,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -325,6 +337,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -342,6 +355,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -360,6 +374,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -383,6 +398,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -401,6 +417,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts index ab41ceb259adb..0e0aef11316b2 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts @@ -38,7 +38,7 @@ export const convertToMovingAverageParams = ( }); export const convertToOtherParentPipelineAggColumns = ( - { agg, dataView, aggs }: ExtendedColumnConverterArgs, + { agg, dataView, aggs, visType }: ExtendedColumnConverterArgs, reducedTimeRange?: string ): FormulaColumn | [ParentPipelineAggColumn, AggBasedColumn] | null => { const { aggType } = agg; @@ -63,7 +63,7 @@ export const convertToOtherParentPipelineAggColumns = ( } if (PIPELINE_AGGS.includes(metric.aggType)) { - const formula = getFormulaForPipelineAgg({ agg, aggs, dataView }); + const formula = getFormulaForPipelineAgg({ agg, aggs, dataView, visType }); if (!formula) { return null; } @@ -71,7 +71,7 @@ export const convertToOtherParentPipelineAggColumns = ( return createFormulaColumn(formula, agg); } - const subMetric = convertMetricToColumns(metric, dataView, aggs); + const subMetric = convertMetricToColumns({ agg: metric, dataView, aggs, visType }); if (subMetric === null) { return null; @@ -90,7 +90,7 @@ export const convertToOtherParentPipelineAggColumns = ( }; export const convertToCumulativeSumAggColumn = ( - { agg, dataView, aggs }: ExtendedColumnConverterArgs, + { agg, dataView, aggs, visType }: ExtendedColumnConverterArgs, reducedTimeRange?: string ): | FormulaColumn @@ -119,7 +119,7 @@ export const convertToCumulativeSumAggColumn = ( // create column for sum or count const subMetric = convertMetricAggregationColumnWithoutSpecialParams( subAgg, - { agg: metric as SchemaConfig, dataView }, + { agg: metric as SchemaConfig, dataView, visType }, reducedTimeRange ); @@ -144,7 +144,7 @@ export const convertToCumulativeSumAggColumn = ( subMetric, ]; } else { - const formula = getFormulaForPipelineAgg({ agg, aggs, dataView }); + const formula = getFormulaForPipelineAgg({ agg, aggs, dataView, visType }); if (!formula) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts index 3b7e8ad7e797f..0ef5d07236d60 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts @@ -18,6 +18,7 @@ jest.mock('../metrics/formula', () => ({ })); describe('convertToColumnInPercentageMode', () => { + const visType = 'heatmap'; const formula = 'average(some_field)'; const dataView = stubLogstashDataView; @@ -42,7 +43,7 @@ describe('convertToColumnInPercentageMode', () => { test('should return null if it is not possible to build the valid formula', () => { mockGetFormulaForAgg.mockReturnValue(null); - expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, {})).toBeNull(); + expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg], visType }, {})).toBeNull(); }); test('should return percentage mode over range formula if min and max was passed', () => { @@ -51,7 +52,7 @@ describe('convertToColumnInPercentageMode', () => { params: { format: { id: 'percent' }, formula: `((${formula}) - 0) / (100 - 0)` }, }; expect( - convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, { min: 0, max: 100 }) + convertToColumnInPercentageMode({ agg, dataView, aggs: [agg], visType }, { min: 0, max: 100 }) ).toEqual(expect.objectContaining(formulaColumn)); }); @@ -60,7 +61,7 @@ describe('convertToColumnInPercentageMode', () => { operationType: 'formula', params: { format: { id: 'percent' }, formula: `(${formula}) / 10000` }, }; - expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, {})).toEqual( + expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg], visType }, {})).toEqual( expect.objectContaining(formulaColumn) ); }); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts index b4cf7f141e928..adfab7f55d1c4 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts @@ -24,6 +24,7 @@ jest.mock('../utils', () => ({ })); describe('convertToPercentileColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const field = dataView.fields[0].displayName; const aggId = 'pr.10'; @@ -67,23 +68,27 @@ describe('convertToPercentileColumn', () => { test.each< [string, Parameters, Partial | null] >([ - ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView }], null], + ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView, visType }], null], [ 'null if no value', - [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView }], + [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView, visType }], + null, + ], + ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView, visType }], null], + ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView, visType }], null], + [ + 'null if aggId is invalid', + [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView, visType }], null, ], - ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView }], null], - ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView }], null], - ['null if aggId is invalid', [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView }], null], [ 'null if values are undefined', - [{ agg: { ...agg, aggParams: { percents: undefined, field } }, dataView }], + [{ agg: { ...agg, aggParams: { percents: undefined, field } }, dataView, visType }], null, ], [ 'null if values are empty', - [{ agg: { ...agg, aggParams: { percents: [], field } }, dataView }], + [{ agg: { ...agg, aggParams: { percents: [], field } }, dataView, visType }], null, ], ])('should return %s', (_, input, expected) => { @@ -96,7 +101,7 @@ describe('convertToPercentileColumn', () => { test('should return null if field is not specified', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToPercentileColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -105,13 +110,13 @@ describe('convertToPercentileColumn', () => { mockGetFieldByName.mockReturnValueOnce(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToPercentileColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); }); test('should return percentile rank column for percentiles', () => { - expect(convertToPercentileColumn({ agg, dataView })).toEqual( + expect(convertToPercentileColumn({ agg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', @@ -126,7 +131,7 @@ describe('convertToPercentileColumn', () => { }); test('should return percentile rank column for single percentile', () => { - expect(convertToPercentileColumn({ agg: singlePercentileRankAgg, dataView })).toEqual( + expect(convertToPercentileColumn({ agg: singlePercentileRankAgg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts index de9d4e088b636..9989db1c5dda7 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts @@ -51,6 +51,7 @@ const getPercent = ( export const convertToPercentileColumn = ( { + visType, agg, dataView, }: CommonColumnConverterArgs, @@ -74,7 +75,7 @@ export const convertToPercentileColumn = ( } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts index 8a696d51d871b..afeaa9899d107 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts @@ -24,6 +24,7 @@ jest.mock('../utils', () => ({ })); describe('convertToPercentileRankColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const field = dataView.fields[0].displayName; const aggId = 'pr.10'; @@ -71,23 +72,27 @@ describe('convertToPercentileRankColumn', () => { Partial | null ] >([ - ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView }], null], + ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView, visType }], null], [ 'null if no value', - [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView }], + [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView, visType }], + null, + ], + ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView, visType }], null], + ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView, visType }], null], + [ + 'null if aggId is invalid', + [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView, visType }], null, ], - ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView }], null], - ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView }], null], - ['null if aggId is invalid', [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView }], null], [ 'null if values are undefined', - [{ agg: { ...agg, aggParams: { values: undefined, field } }, dataView }], + [{ agg: { ...agg, aggParams: { values: undefined, field } }, dataView, visType }], null, ], [ 'null if values are empty', - [{ agg: { ...agg, aggParams: { values: [], field } }, dataView }], + [{ agg: { ...agg, aggParams: { values: [], field } }, dataView, visType }], null, ], ])('should return %s', (_, input, expected) => { @@ -100,7 +105,7 @@ describe('convertToPercentileRankColumn', () => { test('should return null if field is not specified', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToPercentileRankColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileRankColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -109,13 +114,13 @@ describe('convertToPercentileRankColumn', () => { mockGetFieldByName.mockReturnValueOnce(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToPercentileRankColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileRankColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); }); test('should return percentile rank column for percentile ranks', () => { - expect(convertToPercentileRankColumn({ agg, dataView })).toEqual( + expect(convertToPercentileRankColumn({ agg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', @@ -130,7 +135,9 @@ describe('convertToPercentileRankColumn', () => { }); test('should return percentile rank column for single percentile rank', () => { - expect(convertToPercentileRankColumn({ agg: singlePercentileRankAgg, dataView })).toEqual( + expect( + convertToPercentileRankColumn({ agg: singlePercentileRankAgg, dataView, visType }) + ).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts index 5124a26543552..8fb55789dd6a7 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts @@ -50,6 +50,7 @@ const getPercent = ( export const convertToPercentileRankColumn = ( { + visType, agg, dataView, }: CommonColumnConverterArgs, @@ -69,7 +70,7 @@ export const convertToPercentileRankColumn = ( } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts index 8f535c28c8264..5a754fd1c9466 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts @@ -60,7 +60,6 @@ describe('convertToRangeColumn', () => { params: { type: RANGE_MODES.Histogram, maxBars: 'auto', - ranges: [], }, }, ], diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts index 6a9f96fd5ad1e..98200c321935c 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts @@ -27,18 +27,17 @@ export const convertToRangeParams = ( return { type: RANGE_MODES.Histogram, maxBars: aggParams.maxBars ?? 'auto', - ranges: [], + includeEmptyRows: aggParams.min_doc_count, }; } else { return { type: RANGE_MODES.Range, maxBars: 'auto', - ranges: - aggParams.ranges?.map((range) => ({ - label: range.label, - from: range.from ?? null, - to: range.to ?? null, - })) ?? [], + ranges: aggParams.ranges?.map((range) => ({ + label: range.label, + from: range.from ?? null, + to: range.to ?? null, + })), }; } }; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts index 759620650b8a6..6adde7004b69a 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts @@ -23,6 +23,7 @@ jest.mock('../../../vis_schemas', () => ({ })); describe('convertToSiblingPipelineColumns', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const aggId = 'agg-id-1'; const agg: SchemaConfig = { @@ -46,7 +47,12 @@ describe('convertToSiblingPipelineColumns', () => { test('should return null if aggParams are not defined', () => { expect( - convertToSiblingPipelineColumns({ agg: { ...agg, aggParams: undefined }, aggs: [], dataView }) + convertToSiblingPipelineColumns({ + agg: { ...agg, aggParams: undefined }, + aggs: [], + dataView, + visType, + }) ).toBeNull(); expect(mockConvertMetricToColumns).toBeCalledTimes(0); }); @@ -57,6 +63,7 @@ describe('convertToSiblingPipelineColumns', () => { agg: { ...agg, aggParams: { customMetric: undefined } }, aggs: [], dataView, + visType, }) ).toBeNull(); expect(mockConvertMetricToColumns).toBeCalledTimes(0); @@ -64,7 +71,7 @@ describe('convertToSiblingPipelineColumns', () => { test('should return null if sibling agg is not supported', () => { mockConvertMetricToColumns.mockReturnValue(null); - expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView })).toBeNull(); + expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView, visType })).toBeNull(); expect(mockConvertToSchemaConfig).toBeCalledTimes(1); expect(mockConvertMetricToColumns).toBeCalledTimes(1); }); @@ -72,7 +79,7 @@ describe('convertToSiblingPipelineColumns', () => { test('should return column', () => { const column = { operationType: 'formula' }; mockConvertMetricToColumns.mockReturnValue([column]); - expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView })).toEqual(column); + expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView, visType })).toEqual(column); expect(mockConvertToSchemaConfig).toBeCalledTimes(1); expect(mockConvertMetricToColumns).toBeCalledTimes(1); }); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts index a8389cb8601e4..c77500a55d5d1 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts @@ -22,11 +22,12 @@ export const convertToSiblingPipelineColumns = ( return null; } - const customMetricColumn = convertMetricToColumns( - { ...convertToSchemaConfig(aggParams.customMetric), label, aggId }, - columnConverterArgs.dataView, - columnConverterArgs.aggs - ); + const customMetricColumn = convertMetricToColumns({ + agg: { ...convertToSchemaConfig(aggParams.customMetric), label, aggId }, + dataView: columnConverterArgs.dataView, + aggs: columnConverterArgs.aggs, + visType: columnConverterArgs.visType, + }); if (!customMetricColumn) { return null; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts index cbb1f03a6dc2e..c786d6b8c3a6f 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts @@ -22,6 +22,7 @@ jest.mock('../utils', () => ({ })); describe('convertToStdDeviationFormulaColumns', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const stdLowerAggId = 'agg-id.std_lower'; const stdUpperAggId = 'agg-id.std_upper'; @@ -51,22 +52,25 @@ describe('convertToStdDeviationFormulaColumns', () => { test.each< [string, Parameters, Partial | null] - >([['null if no aggId is passed', [{ agg: { ...agg, aggId: undefined }, dataView }], null]])( - 'should return %s', - (_, input, expected) => { - if (expected === null) { - expect(convertToStdDeviationFormulaColumns(...input)).toBeNull(); - } else { - expect(convertToStdDeviationFormulaColumns(...input)).toEqual( - expect.objectContaining(expected) - ); - } + >([ + [ + 'null if no aggId is passed', + [{ agg: { ...agg, aggId: undefined }, dataView, visType }], + null, + ], + ])('should return %s', (_, input, expected) => { + if (expected === null) { + expect(convertToStdDeviationFormulaColumns(...input)).toBeNull(); + } else { + expect(convertToStdDeviationFormulaColumns(...input)).toEqual( + expect.objectContaining(expected) + ); } - ); + }); test('should return null if field is not present', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToStdDeviationFormulaColumns({ agg, dataView })).toBeNull(); + expect(convertToStdDeviationFormulaColumns({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -74,14 +78,14 @@ describe('convertToStdDeviationFormulaColumns', () => { test("should return null if field doesn't exist in dataView", () => { mockGetFieldByName.mockReturnValue(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToStdDeviationFormulaColumns({ agg, dataView })).toBeNull(); + expect(convertToStdDeviationFormulaColumns({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); }); test('should return null if agg id is invalid', () => { expect( - convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: 'some-id' }, dataView }) + convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: 'some-id' }, dataView, visType }) ).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); @@ -89,7 +93,11 @@ describe('convertToStdDeviationFormulaColumns', () => { test('should return formula column for lower std deviation', () => { expect( - convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: stdLowerAggId }, dataView }) + convertToStdDeviationFormulaColumns({ + agg: { ...agg, aggId: stdLowerAggId }, + dataView, + visType, + }) ).toEqual( expect.objectContaining({ label, @@ -102,7 +110,11 @@ describe('convertToStdDeviationFormulaColumns', () => { test('should return formula column for upper std deviation', () => { expect( - convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: stdUpperAggId }, dataView }) + convertToStdDeviationFormulaColumns({ + agg: { ...agg, aggId: stdUpperAggId }, + dataView, + visType, + }) ).toEqual( expect.objectContaining({ label, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts index f2c218d429bdf..fe4e854759d8f 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts @@ -50,7 +50,7 @@ export const getStdDeviationFormula = ( }; export const convertToStdDeviationFormulaColumns = ( - { agg, dataView }: CommonColumnConverterArgs, + { visType, agg, dataView }: CommonColumnConverterArgs, reducedTimeRange?: string ) => { const { aggId } = agg; @@ -64,7 +64,7 @@ export const convertToStdDeviationFormulaColumns = ( return null; } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts index 17a8ccf26c369..61f3f3961b6dc 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts @@ -18,10 +18,12 @@ interface AggWithFormula { formula: string; } +type SupportedDataTypes = { [key: string]: readonly string[] } & { default: readonly string[] }; + export type AggOptions = { isFullReference: boolean; isFieldRequired: boolean; - supportedDataTypes: readonly string[]; + supportedDataTypes: SupportedDataTypes; } & (T extends Exclude ? Agg : AggWithFormula); // list of supported TSVB aggregation types in Lens @@ -62,9 +64,9 @@ export type SupportedMetrics = LocalSupportedMetrics & { [Key in UnsupportedSupportedMetrics]?: null; }; -const supportedDataTypesWithDate = ['number', 'date', 'histogram'] as const; -const supportedDataTypes = ['number', 'histogram'] as const; -const extendedSupportedDataTypes = [ +const supportedDataTypesWithDate: readonly string[] = ['number', 'date', 'histogram']; +const supportedDataTypes: readonly string[] = ['number', 'histogram']; +const extendedSupportedDataTypes: readonly string[] = [ 'string', 'boolean', 'number', @@ -74,44 +76,44 @@ const extendedSupportedDataTypes = [ 'date', 'date_range', 'murmur3', -] as const; +]; export const SUPPORTED_METRICS: SupportedMetrics = { avg: { name: 'average', isFullReference: false, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, cardinality: { name: 'unique_count', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, count: { name: 'count', isFullReference: false, isFieldRequired: false, - supportedDataTypes: [], + supportedDataTypes: { default: ['number'] }, }, moving_avg: { name: 'moving_average', isFullReference: true, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, derivative: { name: 'differences', isFullReference: true, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, cumulative_sum: { name: 'cumulative_sum', isFullReference: true, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, avg_bucket: { name: 'formula', @@ -119,7 +121,7 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_average', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, max_bucket: { name: 'formula', @@ -127,7 +129,7 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_max', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, min_bucket: { name: 'formula', @@ -135,7 +137,7 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_min', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, sum_bucket: { name: 'formula', @@ -143,79 +145,91 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_sum', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, max: { name: 'max', isFullReference: false, isFieldRequired: true, - supportedDataTypes: supportedDataTypesWithDate, + supportedDataTypes: { + default: ['number'], + heatmap: ['number'], + line: ['number'], + area: ['number'], + histogram: ['number'], + }, }, min: { name: 'min', isFullReference: false, isFieldRequired: true, - supportedDataTypes: supportedDataTypesWithDate, + supportedDataTypes: { + default: supportedDataTypesWithDate, + heatmap: ['number'], + line: ['number'], + area: ['number'], + histogram: ['number'], + }, }, percentiles: { name: 'percentile', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, single_percentile: { name: 'percentile', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, percentile_ranks: { name: 'percentile_rank', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, single_percentile_rank: { name: 'percentile_rank', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, sum: { name: 'sum', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, top_hits: { name: 'last_value', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, top_metrics: { name: 'last_value', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, value_count: { name: 'count', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, std_dev: { name: 'standard_deviation', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, median: { name: 'median', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, } as const; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts index d214ec74b09b1..516ad6b196095 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts @@ -23,6 +23,7 @@ jest.mock('../../../vis_schemas', () => ({ })); describe('convertToDateHistogramColumn', () => { + const visType = 'heatmap'; const aggId = `some-id`; const aggParams: AggParamsTerms = { field: stubLogstashDataView.fields[0].name, @@ -79,6 +80,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -95,6 +97,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -107,6 +110,8 @@ describe('convertToDateHistogramColumn', () => { size: 5, include: [], exclude: [], + includeIsRegex: false, + excludeIsRegex: false, parentFormat: { id: 'terms' }, orderBy: { type: 'alphabetical' }, orderDirection: 'asc', @@ -123,6 +128,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -135,6 +141,8 @@ describe('convertToDateHistogramColumn', () => { size: 5, include: [], exclude: [], + includeIsRegex: false, + excludeIsRegex: false, parentFormat: { id: 'terms' }, orderBy: { type: 'column', columnId: metricColumns[0].columnId }, orderAgg: metricColumns[0], @@ -152,6 +160,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -170,6 +179,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -188,6 +198,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -208,6 +219,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -220,6 +232,8 @@ describe('convertToDateHistogramColumn', () => { size: 5, include: [], exclude: [], + includeIsRegex: false, + excludeIsRegex: false, parentFormat: { id: 'terms' }, orderBy: { type: 'custom' }, orderAgg: metricColumns[0], diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts index 0a50390ec469e..a54a3857e20f6 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts @@ -23,6 +23,7 @@ const getOrderByWithAgg = ({ agg, dataView, aggs, + visType, metricColumns, }: CommonBucketConverterArgs): OrderByWithAgg | null => { if (!agg.aggParams) { @@ -37,11 +38,12 @@ const getOrderByWithAgg = ({ if (!agg.aggParams.orderAgg) { return null; } - const orderMetricColumn = convertMetricToColumns( - convertToSchemaConfig(agg.aggParams.orderAgg), + const orderMetricColumn = convertMetricToColumns({ + agg: convertToSchemaConfig(agg.aggParams.orderAgg), dataView, - aggs - ); + aggs, + visType, + }); if (!orderMetricColumn) { return null; } @@ -68,35 +70,43 @@ const getOrderByWithAgg = ({ }; }; +const filterOutEmptyValues = (values: string | Array): number[] | string[] => { + if (typeof values === 'string') { + return Boolean(values) ? [values] : []; + } + + return values.filter((v): v is string | number => { + if (typeof v === 'string') { + return Boolean(v); + } + return true; + }) as string[] | number[]; +}; + export const convertToTermsParams = ({ agg, dataView, aggs, metricColumns, + visType, }: CommonBucketConverterArgs): TermsParams | null => { if (!agg.aggParams) { return null; } - const orderByWithAgg = getOrderByWithAgg({ agg, dataView, aggs, metricColumns }); + const orderByWithAgg = getOrderByWithAgg({ agg, dataView, aggs, metricColumns, visType }); if (orderByWithAgg === null) { return null; } + const exclude = agg.aggParams.exclude ? filterOutEmptyValues(agg.aggParams.exclude) : []; + const include = agg.aggParams.include ? filterOutEmptyValues(agg.aggParams.include) : []; return { size: agg.aggParams.size ?? 10, - include: agg.aggParams.include - ? Array.isArray(agg.aggParams.include) - ? agg.aggParams.include - : [agg.aggParams.include] - : [], - includeIsRegex: agg.aggParams.includeIsRegex, - exclude: agg.aggParams.exclude - ? Array.isArray(agg.aggParams.exclude) - ? agg.aggParams.exclude - : [agg.aggParams.exclude] - : [], - excludeIsRegex: agg.aggParams.excludeIsRegex, + include, + exclude, + includeIsRegex: Boolean(include.length && agg.aggParams.includeIsRegex), + excludeIsRegex: Boolean(exclude.length && agg.aggParams.excludeIsRegex), otherBucket: agg.aggParams.otherBucket, orderDirection: agg.aggParams.order?.value ?? 'desc', parentFormat: { id: 'terms' }, @@ -107,7 +117,7 @@ export const convertToTermsParams = ({ export const convertToTermsColumn = ( aggId: string, - { agg, dataView, aggs, metricColumns }: CommonBucketConverterArgs, + { agg, dataView, aggs, metricColumns, visType }: CommonBucketConverterArgs, label: string, isSplit: boolean ): TermsColumn | null => { @@ -121,7 +131,7 @@ export const convertToTermsColumn = ( return null; } - const params = convertToTermsParams({ agg, dataView, aggs, metricColumns }); + const params = convertToTermsParams({ agg, dataView, aggs, metricColumns, visType }); if (!params) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts index 8e6f9ec9443bb..97ccba39303fc 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts @@ -64,6 +64,7 @@ export interface CommonColumnConverterArgs< > { agg: SchemaConfig; dataView: DataView; + visType: string; } export interface ExtendedColumnConverterArgs< @@ -75,6 +76,7 @@ export interface ExtendedColumnConverterArgs< export interface CommonBucketConverterArgs< Agg extends SupportedAggregation = SupportedAggregation > { + visType: string; agg: SchemaConfig; dataView: DataView; metricColumns: AggBasedColumn[]; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts index 95e128e22b092..72cd07ba03f7c 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts @@ -29,7 +29,7 @@ jest.mock('../utils', () => ({ })); const dataView = stubLogstashDataView; - +const visType = 'heatmap'; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ { @@ -97,7 +97,7 @@ describe('getFormulaForPipelineAgg', () => { test.each<[string, Parameters, () => void, string | null]>([ [ 'null if custom metric is invalid', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValue(null); }, @@ -105,7 +105,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'null if custom metric type is not supported', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValue({ aggType: METRIC_TYPES.GEO_BOUNDS, @@ -115,7 +115,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is parent pipeline agg and custom metric is valid and supported pipeline agg', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg .mockReturnValueOnce({ @@ -135,7 +135,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is parent pipeline agg and custom metric is valid and supported not pipeline agg', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ aggType: METRIC_TYPES.AVG, @@ -149,7 +149,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is parent pipeline agg and custom metric is valid and supported percentile rank agg', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ aggType: METRIC_TYPES.PERCENTILE_RANKS, @@ -163,7 +163,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is sibling pipeline agg and custom metric is valid and supported agg', - [{ agg: aggs[1] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[1] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ aggType: METRIC_TYPES.AVG, @@ -212,6 +212,7 @@ describe('getFormulaForPipelineAgg', () => { agg: aggs[1] as SchemaConfig, aggs, dataView, + visType, }); expect(agg).toBeNull(); }); @@ -244,6 +245,7 @@ describe('getFormulaForPipelineAgg', () => { agg: aggs[1] as SchemaConfig, aggs, dataView, + visType, }); expect(agg).toBeNull(); }); @@ -270,6 +272,7 @@ describe('getFormulaForAgg', () => { agg: { ...aggs[0], aggType: METRIC_TYPES.GEO_BOUNDS, aggParams: { field } }, aggs, dataView, + visType, }, ], () => {}, @@ -277,7 +280,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct pipeline formula if agg is valid pipeline agg', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockIsPipeline.mockReturnValue(true); mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ @@ -292,7 +295,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct percentile formula if agg is valid percentile agg', - [{ agg: aggs[2], aggs, dataView }], + [{ agg: aggs[2], aggs, dataView, visType }], () => { mockIsPercentileAgg.mockReturnValue(true); }, @@ -300,7 +303,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct percentile rank formula if agg is valid percentile rank agg', - [{ agg: aggs[3], aggs, dataView }], + [{ agg: aggs[3], aggs, dataView, visType }], () => { mockIsPercentileRankAgg.mockReturnValue(true); }, @@ -308,7 +311,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct standart deviation formula if agg is valid standart deviation agg', - [{ agg: aggs[4], aggs, dataView }], + [{ agg: aggs[4], aggs, dataView, visType }], () => { mockIsStdDevAgg.mockReturnValue(true); }, @@ -316,7 +319,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct metric formula if agg is valid other metric agg', - [{ agg: aggs[5], aggs, dataView }], + [{ agg: aggs[5], aggs, dataView, visType }], () => {}, 'average(bytes)', ], @@ -395,6 +398,7 @@ describe('getFormulaForAgg', () => { >, aggs, dataView, + visType, }); expect(result).toBeNull(); }); @@ -467,6 +471,7 @@ describe('getFormulaForAgg', () => { >, aggs, dataView, + visType, }); expect(result).toBeNull(); } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts index 276ac54e2fc3d..4492cd58ac230 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts @@ -66,7 +66,7 @@ const isDataViewField = (field: string | DataViewField): field is DataViewField return false; }; -const isValidAgg = (agg: SchemaConfig, dataView: DataView) => { +const isValidAgg = (visType: string, agg: SchemaConfig, dataView: DataView) => { const aggregation = SUPPORTED_METRICS[agg.aggType]; if (!aggregation) { return false; @@ -77,7 +77,7 @@ const isValidAgg = (agg: SchemaConfig, dataView: DataView) => { } const sourceField = getFieldNameFromField(agg.aggParams?.field); const field = dataView.getFieldByName(sourceField!); - if (!isFieldValid(field, aggregation)) { + if (!isFieldValid(visType, field, aggregation)) { return false; } } @@ -86,13 +86,14 @@ const isValidAgg = (agg: SchemaConfig, dataView: DataView) => { }; const getFormulaForAggsWithoutParams = ( + visType: string, agg: SchemaConfig, dataView: DataView, selector: string | undefined, reducedTimeRange?: string ) => { const op = SUPPORTED_METRICS[agg.aggType]; - if (!isValidAgg(agg, dataView) || !op) { + if (!isValidAgg(visType, agg, dataView) || !op) { return null; } @@ -101,6 +102,7 @@ const getFormulaForAggsWithoutParams = ( }; const getFormulaForPercentileRanks = ( + visType: string, agg: SchemaConfig, dataView: DataView, selector: string | undefined, @@ -108,7 +110,7 @@ const getFormulaForPercentileRanks = ( ) => { const value = Number(agg.aggId?.split('.')[1]); const op = SUPPORTED_METRICS[agg.aggType]; - if (!isValidAgg(agg, dataView) || !op) { + if (!isValidAgg(visType, agg, dataView) || !op) { return null; } @@ -117,6 +119,7 @@ const getFormulaForPercentileRanks = ( }; const getFormulaForPercentile = ( + visType: string, agg: SchemaConfig, dataView: DataView, selector: string, @@ -124,7 +127,7 @@ const getFormulaForPercentile = ( ) => { const percentile = Number(agg.aggId?.split('.')[1]); const op = SUPPORTED_METRICS[agg.aggType]; - if (!isValidAgg(agg, dataView) || !op) { + if (!isValidAgg(visType, agg, dataView) || !op) { return null; } @@ -138,6 +141,7 @@ const getFormulaForSubMetric = ({ agg, dataView, aggs, + visType, }: ExtendedColumnConverterArgs): string | null => { const op = SUPPORTED_METRICS[agg.aggType]; if (!op) { @@ -148,12 +152,13 @@ const getFormulaForSubMetric = ({ PARENT_PIPELINE_OPS.includes(op.name) || SIBLING_PIPELINE_AGGS.includes(agg.aggType as METRIC_TYPES) ) { - return getFormulaForPipelineAgg({ agg: agg as PipelineAggs, aggs, dataView }); + return getFormulaForPipelineAgg({ agg: agg as PipelineAggs, aggs, dataView, visType }); } if (METRIC_OPS_WITHOUT_PARAMS.includes(op.name)) { const metricAgg = agg as MetricAggsWithoutParams; return getFormulaForAggsWithoutParams( + visType, metricAgg, dataView, metricAgg.aggParams && 'field' in metricAgg.aggParams @@ -168,6 +173,7 @@ const getFormulaForSubMetric = ({ const percentileRanksAgg = agg as SchemaConfig; return getFormulaForPercentileRanks( + visType, percentileRanksAgg, dataView, percentileRanksAgg.aggParams?.field @@ -181,6 +187,7 @@ export const getFormulaForPipelineAgg = ({ agg, dataView, aggs, + visType, }: ExtendedColumnConverterArgs< | METRIC_TYPES.CUMULATIVE_SUM | METRIC_TYPES.DERIVATIVE @@ -205,6 +212,7 @@ export const getFormulaForPipelineAgg = ({ agg: metricAgg, aggs, dataView, + visType, }); if (subFormula === null) { return null; @@ -222,13 +230,15 @@ export const getFormulaForAgg = ({ agg, aggs, dataView, + visType, }: ExtendedColumnConverterArgs) => { if (isPipeline(agg)) { - return getFormulaForPipelineAgg({ agg, aggs, dataView }); + return getFormulaForPipelineAgg({ agg, aggs, dataView, visType }); } if (isPercentileAgg(agg)) { return getFormulaForPercentile( + visType, agg, dataView, getFieldNameFromField(agg.aggParams?.field) ?? '' @@ -237,6 +247,7 @@ export const getFormulaForAgg = ({ if (isPercentileRankAgg(agg)) { return getFormulaForPercentileRanks( + visType, agg, dataView, getFieldNameFromField(agg.aggParams?.field) ?? '' @@ -244,13 +255,14 @@ export const getFormulaForAgg = ({ } if (isStdDevAgg(agg) && agg.aggId) { - if (!isValidAgg(agg, dataView)) { + if (!isValidAgg(visType, agg, dataView)) { return null; } return getStdDeviationFormula(agg.aggId, getFieldNameFromField(agg.aggParams?.field) ?? ''); } return getFormulaForAggsWithoutParams( + visType, agg, dataView, isMetricWithField(agg) ? getFieldNameFromField(agg.aggParams?.field) ?? '' : '' diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts index 1cf3ff0b84064..c7674bf6603c0 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts @@ -9,6 +9,7 @@ import { METRIC_TYPES } from '@kbn/data-plugin/common'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { SchemaConfig } from '../../..'; +import { ExtendedColumnConverterArgs } from '../convert'; import { convertMetricToColumns } from './metrics'; const mockConvertMetricAggregationColumnWithoutSpecialParams = jest.fn(); @@ -37,6 +38,8 @@ jest.mock('../convert', () => ({ convertToColumnInPercentageMode: jest.fn(() => mockConvertToColumnInPercentageMode()), })); +const visType = 'heatmap'; + describe('convertMetricToColumns invalid cases', () => { const dataView = stubLogstashDataView; @@ -55,13 +58,18 @@ describe('convertMetricToColumns invalid cases', () => { mockConvertToCumulativeSumAggColumn.mockReturnValue(null); }); + const aggs: ExtendedColumnConverterArgs['aggs'] = []; + test.each<[string, Parameters, null, jest.Mock | undefined]>([ [ 'null if agg is not supported', [ - { aggType: METRIC_TYPES.GEO_BOUNDS } as unknown as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.GEO_BOUNDS } as unknown as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -70,9 +78,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg AVG is not valid', [ - { aggType: METRIC_TYPES.AVG } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -81,9 +92,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MIN is not valid', [ - { aggType: METRIC_TYPES.MIN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN } as SchemaConfig, + dataView, + aggs: [], + visType, + }, { isPercentageMode: false }, ], null, @@ -92,9 +106,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MAX is not valid', [ - { aggType: METRIC_TYPES.MAX } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -103,9 +120,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SUM is not valid', [ - { aggType: METRIC_TYPES.SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -114,9 +134,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg COUNT is not valid', [ - { aggType: METRIC_TYPES.COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -125,9 +148,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg CARDINALITY is not valid', [ - { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -136,9 +162,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg VALUE_COUNT is not valid', [ - { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -147,9 +176,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MEDIAN is not valid', [ - { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -158,9 +190,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg STD_DEV is not valid', [ - { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -169,9 +204,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg PERCENTILES is not valid', [ - { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -180,9 +218,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SINGLE_PERCENTILE is not valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -191,9 +232,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg PERCENTILE_RANKS is not valid', [ - { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -202,9 +246,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SINGLE_PERCENTILE_RANK is not valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -213,9 +260,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg TOP_HITS is not valid', [ - { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -224,9 +274,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg TOP_METRICS is not valid', [ - { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -235,9 +288,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg CUMULATIVE_SUM is not valid', [ - { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -246,9 +302,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg DERIVATIVE is not valid', [ - { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -257,9 +316,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MOVING_FN is not valid', [ - { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -268,9 +330,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SUM_BUCKET is not valid', [ - { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -279,9 +344,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MIN_BUCKET is not valid', [ - { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -290,9 +358,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MAX_BUCKET is not valid', [ - { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -301,9 +372,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg AVG_BUCKET is not valid', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs: [], + visType, + }, { isPercentageMode: false }, ], null, @@ -312,9 +386,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SERIAL_DIFF is not valid', [ - { aggType: METRIC_TYPES.SERIAL_DIFF } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SERIAL_DIFF } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -330,6 +407,7 @@ describe('convertMetricToColumns invalid cases', () => { }); describe('convertMetricToColumns valid cases', () => { const dataView = stubLogstashDataView; + const aggs: ExtendedColumnConverterArgs['aggs'] = []; beforeEach(() => { jest.clearAllMocks(); @@ -353,9 +431,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg AVG is valid', [ - { aggType: METRIC_TYPES.AVG } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -364,9 +445,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MIN is valid', [ - { aggType: METRIC_TYPES.MIN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -375,9 +459,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MAX is valid', [ - { aggType: METRIC_TYPES.MAX } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -386,9 +473,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SUM is valid', [ - { aggType: METRIC_TYPES.SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -397,9 +487,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg COUNT is valid', [ - { aggType: METRIC_TYPES.COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -408,9 +501,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg CARDINALITY is valid', [ - { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -419,9 +515,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg VALUE_COUNT is valid', [ - { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -430,9 +529,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MEDIAN is valid', [ - { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -441,9 +543,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg STD_DEV is valid', [ - { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -452,9 +557,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg PERCENTILES is valid', [ - { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -463,9 +571,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SINGLE_PERCENTILE is valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -474,9 +585,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg PERCENTILE_RANKS is valid', [ - { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -485,9 +599,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SINGLE_PERCENTILE_RANK is valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -496,9 +613,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg TOP_HITS is valid', [ - { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -507,9 +627,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg TOP_METRICS is valid', [ - { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -518,9 +641,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg CUMULATIVE_SUM is valid', [ - { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -529,9 +655,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg DERIVATIVE is valid', [ - { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -540,9 +669,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MOVING_FN is valid', [ - { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -551,9 +683,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SUM_BUCKET is valid', [ - { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -562,9 +697,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MIN_BUCKET is valid', [ - { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -573,9 +711,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MAX_BUCKET is valid', [ - { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -584,9 +725,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg AVG_BUCKET is valid', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -595,9 +739,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'column in percentage mode without range if percentageMode is enabled ', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: true, min: 0, max: 100 }, ], result, @@ -606,9 +753,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'column in percentage mode with range if percentageMode is enabled ', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: true, min: 0, max: 100 }, ], result, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts index be4c92cd4ec7f..5d765a6f286ba 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts @@ -7,8 +7,7 @@ */ import { METRIC_TYPES } from '@kbn/data-plugin/common'; -import type { DataView } from '@kbn/data-views-plugin/common'; -import { PercentageModeConfig, SchemaConfig } from '../../..'; +import { PercentageModeConfig } from '../../..'; import { convertMetricAggregationColumnWithoutSpecialParams, convertToOtherParentPipelineAggColumns, @@ -20,14 +19,13 @@ import { convertToCumulativeSumAggColumn, AggBasedColumn, convertToColumnInPercentageMode, + ExtendedColumnConverterArgs, } from '../convert'; import { SUPPORTED_METRICS } from '../convert/supported_metrics'; import { getValidColumns } from '../utils'; export const convertMetricToColumns = ( - agg: SchemaConfig, - dataView: DataView, - aggs: Array>, + { agg, dataView, aggs, visType }: ExtendedColumnConverterArgs, percentageModeConfig: PercentageModeConfig = { isPercentageMode: false } ): AggBasedColumn[] | null => { const supportedAgg = SUPPORTED_METRICS[agg.aggType]; @@ -38,7 +36,7 @@ export const convertMetricToColumns = ( if (percentageModeConfig.isPercentageMode) { const { isPercentageMode, ...minMax } = percentageModeConfig; - const formulaColumn = convertToColumnInPercentageMode({ agg, dataView, aggs }, minMax); + const formulaColumn = convertToColumnInPercentageMode({ agg, dataView, aggs, visType }, minMax); return getValidColumns(formulaColumn); } @@ -54,6 +52,7 @@ export const convertMetricToColumns = ( const columns = convertMetricAggregationColumnWithoutSpecialParams(supportedAgg, { agg, dataView, + visType, }); return getValidColumns(columns); } @@ -61,6 +60,7 @@ export const convertMetricToColumns = ( const columns = convertToStdDeviationFormulaColumns({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -68,6 +68,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -75,6 +76,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -82,6 +84,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileRankColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -89,6 +92,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileRankColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -97,6 +101,7 @@ export const convertMetricToColumns = ( const columns = convertToLastValueColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -105,6 +110,7 @@ export const convertMetricToColumns = ( agg, dataView, aggs, + visType, }); return getValidColumns(columns); } @@ -114,6 +120,7 @@ export const convertMetricToColumns = ( agg, dataView, aggs, + visType, }); return getValidColumns(columns); } @@ -125,6 +132,7 @@ export const convertMetricToColumns = ( agg, dataView, aggs, + visType, }); return getValidColumns(columns); } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts index 9855ce44b6602..fe6204d1fb2a1 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts @@ -24,6 +24,7 @@ jest.mock('../convert', () => ({ })); describe('getPercentageColumnFormulaColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ @@ -52,7 +53,7 @@ describe('getPercentageColumnFormulaColumn', () => { >([ [ 'null if cannot build formula for provided agg', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockGetFormulaForAgg.mockReturnValue(null); }, @@ -60,7 +61,7 @@ describe('getPercentageColumnFormulaColumn', () => { ], [ 'null if cannot create formula column for provided arguments', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockGetFormulaForAgg.mockReturnValue('test-formula'); mockCreateFormulaColumn.mockReturnValue(null); @@ -69,7 +70,7 @@ describe('getPercentageColumnFormulaColumn', () => { ], [ 'formula column if provided arguments are valid', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockGetFormulaForAgg.mockReturnValue('test-formula'); mockCreateFormulaColumn.mockImplementation((formula) => ({ diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts index 773851a770db4..8d7194d5c25df 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts @@ -14,8 +14,9 @@ export const getPercentageColumnFormulaColumn = ({ agg, aggs, dataView, + visType, }: ExtendedColumnConverterArgs): FormulaColumn | null => { - const metricFormula = getFormulaForAgg({ agg, aggs, dataView }); + const metricFormula = getFormulaForAgg({ agg, aggs, dataView, visType }); if (!metricFormula) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts index 4f7a5ad715215..8a6e70669dcf4 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts @@ -28,6 +28,9 @@ import { GaugeCentralMajorModes, CollapseFunctions, } from '../constants'; +import { ExpressionValueVisDimension } from '../../expression_functions'; + +export type ChartShapes = 'heatmap'; export type CollapseFunction = typeof CollapseFunctions[number]; @@ -183,6 +186,7 @@ export interface ColumnState { summaryRow?: 'none' | 'sum' | 'avg' | 'count' | 'min' | 'max'; alignment?: 'left' | 'right' | 'center'; collapseFn?: CollapseFunction; + palette?: PaletteOutput; } export interface TableVisConfiguration { @@ -276,9 +280,63 @@ export type GaugeVisConfiguration = GaugeState & { layerType: typeof LayerTypes.DATA; }; +export interface HeatmapLegendConfig { + isVisible: boolean; + position: Position; + maxLines?: number; + shouldTruncate?: boolean; + legendSize?: LegendSize; + type: 'heatmap_legend'; +} + +export interface HeatmapGridConfig { + strokeWidth?: number; + strokeColor?: string; + isCellLabelVisible: boolean; + isYAxisLabelVisible: boolean; + isYAxisTitleVisible: boolean; + yTitle?: string; + isXAxisLabelVisible: boolean; + isXAxisTitleVisible: boolean; + xTitle?: string; + type: 'heatmap_grid'; +} +export interface HeatmapArguments { + percentageMode?: boolean; + lastRangeIsRightOpen?: boolean; + showTooltip?: boolean; + highlightInHover?: boolean; + palette?: PaletteOutput; + xAccessor?: string | ExpressionValueVisDimension; + yAccessor?: string | ExpressionValueVisDimension; + valueAccessor?: string | ExpressionValueVisDimension; + splitRowAccessor?: string | ExpressionValueVisDimension; + splitColumnAccessor?: string | ExpressionValueVisDimension; + legend: HeatmapLegendConfig; + gridConfig: HeatmapGridConfig; + ariaLabel?: string; +} + +export type HeatmapLayerState = HeatmapArguments & { + layerId: string; + layerType: LayerType; + valueAccessor?: string; + xAccessor?: string; + yAccessor?: string; + shape: ChartShapes; +}; + +export type Palette = PaletteOutput & { accessor: string }; + +export type HeatmapConfiguration = HeatmapLayerState & { + // need to store the current accessor to reset the color stops at accessor change + palette?: Palette; +}; + export type Configuration = | XYConfiguration | TableVisConfiguration | PartitionVisConfiguration | MetricVisConfiguration - | GaugeVisConfiguration; + | GaugeVisConfiguration + | HeatmapConfiguration; diff --git a/src/plugins/visualizations/common/convert_to_lens/types/params.ts b/src/plugins/visualizations/common/convert_to_lens/types/params.ts index d66822921fb19..4623506496382 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/params.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/params.ts @@ -55,7 +55,7 @@ interface Range { export interface RangeParams extends FormatParams { type: RangeMode; maxBars: 'auto' | number; - ranges: Range[]; + ranges?: Range[]; includeEmptyRows?: boolean; parentFormat?: { id: string; diff --git a/src/plugins/visualizations/common/convert_to_lens/utils.ts b/src/plugins/visualizations/common/convert_to_lens/utils.ts index 6a875bf63bea4..88c2802c421ec 100644 --- a/src/plugins/visualizations/common/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/common/convert_to_lens/utils.ts @@ -18,7 +18,17 @@ export const isAnnotationsLayer = ( export const getIndexPatternIds = (layers: Layer[]) => layers.map(({ indexPatternId }) => indexPatternId); +const isValidFieldType = ( + visType: string, + { supportedDataTypes }: SupportedMetric, + field: DataViewField +) => { + const availableDataTypes = supportedDataTypes[visType] ?? supportedDataTypes.default; + return availableDataTypes.includes(field.type); +}; + export const isFieldValid = ( + visType: string, field: DataViewField | undefined, aggregation: SupportedMetric ): field is DataViewField => { @@ -26,7 +36,7 @@ export const isFieldValid = ( return false; } - if (field && (!field.aggregatable || !aggregation.supportedDataTypes.includes(field.type))) { + if (field && (!field.aggregatable || !isValidFieldType(visType, aggregation, field))) { return false; } diff --git a/src/plugins/visualizations/public/convert_to_lens/index.ts b/src/plugins/visualizations/public/convert_to_lens/index.ts index 73509d49157ae..46fca64199ae1 100644 --- a/src/plugins/visualizations/public/convert_to_lens/index.ts +++ b/src/plugins/visualizations/public/convert_to_lens/index.ts @@ -8,8 +8,10 @@ export { getColumnsFromVis } from './schemas'; export { + convertToFiltersColumn, getPercentageColumnFormulaColumn, getPalette, + getPaletteFromStopsWithColors, getPercentageModeConfig, createStaticValueColumn, } from '../../common/convert_to_lens/lib'; diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts index 54975d08b8486..aa338db367988 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts @@ -70,7 +70,9 @@ describe('getColumnsFromVis', () => { ); const aggConfig = new AggConfig(aggConfigs, {} as AggConfigOptions); - const vis = {} as Vis; + const vis = { + type: { name: 'heatmap' }, + } as Vis; beforeEach(() => { jest.clearAllMocks(); mockGetVisSchemas.mockReturnValue({}); diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index 3a225e540faae..1b44f7cdffda1 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -33,6 +33,7 @@ const areVisSchemasValid = (visSchemas: Schemas, unsupported: Array>, metricsForLayer: Array>, @@ -52,7 +53,7 @@ const createLayer = ( dropEmptyRowsInDateHistogram?: boolean ) => { const metricColumns = metricsForLayer.flatMap((m) => - convertMetricToColumns(m, dataView, allMetrics, percentageModeConfig) + convertMetricToColumns({ agg: m, dataView, aggs: allMetrics, visType }, percentageModeConfig) ); if (metricColumns.includes(null)) { return null; @@ -60,6 +61,7 @@ const createLayer = ( const metricColumnsWithoutNull = metricColumns as AggBasedColumn[]; const { customBucketColumns, customBucketsMap } = getCustomBucketColumns( + visType, customBucketsWithMetricIds, metricColumnsWithoutNull, dataView, @@ -72,6 +74,7 @@ const createLayer = ( } const bucketColumns = getBucketColumns( + visType, visSchemas, buckets, dataView, @@ -84,6 +87,7 @@ const createLayer = ( } const splitBucketColumns = getBucketColumns( + visType, visSchemas, splits, dataView, @@ -181,6 +185,7 @@ export const getColumnsFromVis = ( c.metricIds.some((m) => metricAggIds.includes(m)) ); const layer = createLayer( + vis.type.name, visSchemas, aggs, metrics, @@ -197,6 +202,7 @@ export const getColumnsFromVis = ( } } else { const layer = createLayer( + vis.type.name, visSchemas, aggs, aggs, diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.test.ts b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts index 50f667430a8cb..8c36b28452271 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts @@ -213,6 +213,7 @@ describe('getBucketCollapseFn', () => { describe('getBucketColumns', () => { const dataView = stubLogstashDataView; + const visType = 'heatmap'; beforeEach(() => { jest.clearAllMocks(); @@ -228,7 +229,7 @@ describe('getBucketColumns', () => { [bucketKey]: [], }; - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toEqual([]); + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toEqual([]); expect(mockConvertBucketToColumns).toBeCalledTimes(0); }); @@ -254,7 +255,7 @@ describe('getBucketColumns', () => { }; mockConvertBucketToColumns.mockReturnValueOnce(null); - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toBeNull(); + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toBeNull(); expect(mockConvertBucketToColumns).toBeCalledTimes(1); }); @@ -280,7 +281,7 @@ describe('getBucketColumns', () => { }; mockConvertBucketToColumns.mockReturnValueOnce([null]); - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toBeNull(); + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toBeNull(); expect(mockConvertBucketToColumns).toBeCalledTimes(1); }); test('should return columns', () => { @@ -319,7 +320,7 @@ describe('getBucketColumns', () => { mockConvertBucketToColumns.mockReturnValue(returnValue); - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toEqual([ + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toEqual([ ...returnValue, ...returnValue, ]); @@ -592,6 +593,8 @@ describe('sortColumns', () => { }); describe('getColumnIds', () => { + const visType = 'heatmap'; + const colId1 = '0_agg_id'; const colId2 = '1_agg_id'; const colId3 = '2_agg_id'; @@ -694,6 +697,7 @@ describe('getColumnIds', () => { }); expect( getCustomBucketColumns( + visType, customBucketsWithMetricIds, [ { columnId: 'col-3', meta: { aggId: '3' } }, diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.ts b/src/plugins/visualizations/public/convert_to_lens/utils.ts index ba05d29cdeea9..531746ff86d87 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.ts @@ -63,6 +63,7 @@ export const getBucketCollapseFn = ( }; export const getBucketColumns = ( + visType: string, visSchemas: Schemas, keys: Array, dataView: DataView, @@ -78,6 +79,7 @@ export const getBucketColumns = ( { agg: m, dataView, + visType, metricColumns, aggs: visSchemas.metric as Array>, }, @@ -154,6 +156,7 @@ export const sortColumns = ( export const getColumnIds = (columns: AggBasedColumn[]) => columns.map(({ columnId }) => columnId); export const getCustomBucketColumns = ( + visType: string, customBucketsWithMetricIds: Array<{ customBucket: IAggConfig; metricIds: string[]; @@ -167,7 +170,7 @@ export const getCustomBucketColumns = ( const customBucketsMap: Record = {}; customBucketsWithMetricIds.forEach((customBucketWithMetricIds) => { const customBucketColumn = convertBucketToColumns( - { agg: customBucketWithMetricIds.customBucket, dataView, metricColumns, aggs }, + { agg: customBucketWithMetricIds.customBucket, dataView, metricColumns, aggs, visType }, true, dropEmptyRowsInDateHistogram ); diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx index ef9e8d53a4f11..f2a2a7f8ae000 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.tsx @@ -10,7 +10,7 @@ import _, { get } from 'lodash'; import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; +import { render } from 'react-dom'; import { EuiLoadingChart } from '@elastic/eui'; import { Filter, onlyDisabledFiltersChanged, Query, TimeRange } from '@kbn/es-query'; import type { KibanaExecutionContext, SavedObjectAttributes } from '@kbn/core/public'; @@ -503,7 +503,7 @@ export class VisualizeEmbeddable const { error } = this.getOutput(); if (error) { - this.renderError(this.domNode, error); + render(this.catchError(error), this.domNode); } }) ); @@ -511,9 +511,9 @@ export class VisualizeEmbeddable await this.updateHandler(); } - public renderError(domNode: HTMLElement, error: ErrorLike | string) { + public catchError(error: ErrorLike | string) { if (isFallbackDataView(this.vis.data.indexPattern)) { - render( + return ( , - domNode + /> ); - } else { - render(, domNode); } - return () => unmountComponentAtNode(domNode); + return ; } public destroy() { diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx index b9ff8d98f2ced..8cc220e77c8bc 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx @@ -110,6 +110,7 @@ export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => { visEditorRef={visEditorRef} embeddableId={embeddableId} onAppLeave={onAppLeave} + eventEmitter={eventEmitter} /> ); }; diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx index 480f0c3d36ee1..221cdcc9d8e10 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx @@ -110,6 +110,7 @@ export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => { visEditorRef={visEditorRef} onAppLeave={onAppLeave} embeddableId={embeddableIdValue} + eventEmitter={eventEmitter} /> ); }; diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx index 4598d2d23e613..7fa6418aa261b 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx @@ -7,6 +7,7 @@ */ import './visualize_editor.scss'; +import { EventEmitter } from 'events'; import React, { RefObject, useCallback, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -48,6 +49,7 @@ interface VisualizeEditorCommonProps { originatingPath?: string; visualizationIdFromUrl?: string; embeddableId?: string; + eventEmitter?: EventEmitter; } export const VisualizeEditorCommon = ({ @@ -66,6 +68,7 @@ export const VisualizeEditorCommon = ({ visualizationIdFromUrl, embeddableId, visEditorRef, + eventEmitter, }: VisualizeEditorCommonProps) => { const { services } = useKibana(); @@ -148,6 +151,7 @@ export const VisualizeEditorCommon = ({ visualizationIdFromUrl={visualizationIdFromUrl} embeddableId={embeddableId} onAppLeave={onAppLeave} + eventEmitter={eventEmitter} /> )} {visInstance?.vis?.type?.stage === 'experimental' && diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx index e7512c6dd6473..2deffa0c511b3 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx @@ -7,7 +7,7 @@ */ import React, { memo, useCallback, useMemo, useState, useEffect } from 'react'; - +import { EventEmitter } from 'events'; import { AppMountParameters, OverlayRef } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import useLocalStorage from 'react-use/lib/useLocalStorage'; @@ -40,6 +40,7 @@ interface VisualizeTopNavProps { visualizationIdFromUrl?: string; embeddableId?: string; onAppLeave: AppMountParameters['onAppLeave']; + eventEmitter?: EventEmitter; } const TopNav = ({ @@ -57,6 +58,7 @@ const TopNav = ({ visualizationIdFromUrl, embeddableId, onAppLeave, + eventEmitter, }: VisualizeTopNavProps) => { const { services } = useKibana(); const { TopNavMenu } = services.navigation.ui; @@ -114,7 +116,9 @@ const TopNav = ({ vis.type, vis.params, uiStateJSON?.vis, + uiStateJSON?.table, vis.data.indexPattern, + eventEmitter, ]); const displayEditInLensItem = Boolean(vis.type.navigateToLens && editInLensConfig); @@ -139,6 +143,7 @@ const TopNav = ({ hideLensBadge, setNavigateToLens, showBadge: !hideTryInLensBadge && displayEditInLensItem, + eventEmitter, }, services ); @@ -161,6 +166,7 @@ const TopNav = ({ displayEditInLensItem, hideLensBadge, hideTryInLensBadge, + eventEmitter, ]); const [indexPatterns, setIndexPatterns] = useState([]); const showDatePicker = () => { diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx index 36b92585f1096..cab3d41ff8266 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx +++ b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx @@ -8,6 +8,7 @@ import React from 'react'; import moment from 'moment'; +import EventEmitter from 'events'; import { i18n } from '@kbn/i18n'; import { EuiBetaBadgeProps } from '@elastic/eui'; import { parse } from 'query-string'; @@ -71,6 +72,7 @@ export interface TopNavConfigParams { hideLensBadge: () => void; setNavigateToLens: (flag: boolean) => void; showBadge: boolean; + eventEmitter?: EventEmitter; } const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); @@ -102,6 +104,7 @@ export const getTopNavConfig = ( hideLensBadge, setNavigateToLens, showBadge, + eventEmitter, }: TopNavConfigParams, { data, @@ -301,6 +304,10 @@ export const getTopNavConfig = ( }, }), run: async () => { + // lens doesn't support saved searches, should unlink before transition + if (eventEmitter && visInstance.vis.data.savedSearchId) { + eventEmitter.emit('unlinkFromSavedSearch', false); + } const updatedWithMeta = { ...editInLensConfig, savedObjectId: visInstance.vis.id, diff --git a/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.ts b/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.ts index 8d7f2a8ef61f4..ffd23ec06aea6 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.ts +++ b/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.ts @@ -29,7 +29,7 @@ export const useLinkedSearchUpdates = ( // SearchSource is a promise-based stream of search results that can inherit from other search sources. const { searchSource } = visInstance.vis.data; - const unlinkFromSavedSearch = () => { + const unlinkFromSavedSearch = (showToast: boolean = true) => { const searchSourceParent = savedSearch.searchSource; const searchSourceGrandparent = searchSourceParent?.getParent(); const currentIndex = searchSourceParent?.getField('index'); @@ -44,14 +44,16 @@ export const useLinkedSearchUpdates = ( parentFilters: (searchSourceParent?.getOwnField('filter') as Filter[]) || [], }); - services.toastNotifications.addSuccess( - i18n.translate('visualizations.linkedToSearch.unlinkSuccessNotificationText', { - defaultMessage: `Unlinked from saved search '{searchTitle}'`, - values: { - searchTitle: savedSearch.title, - }, - }) - ); + if (showToast) { + services.toastNotifications.addSuccess( + i18n.translate('visualizations.linkedToSearch.unlinkSuccessNotificationText', { + defaultMessage: `Unlinked from saved search '{searchTitle}'`, + values: { + searchTitle: savedSearch.title, + }, + }) + ); + } }; eventEmitter.on('unlinkFromSavedSearch', unlinkFromSavedSearch); diff --git a/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts b/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts index 3d0b018156ce7..79e43f0df086f 100644 --- a/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts +++ b/test/analytics/tests/instrumented_events/from_the_browser/core_context_providers.ts @@ -91,5 +91,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(event.context).not.to.have.property('cloudId'); } }); + + it('should have the properties provided by the "viewport_size" context provider', async () => { + expect(event.context).to.have.property('viewport_width'); + expect(event.context.viewport_width).to.be.a('number'); + expect(event.context).to.have.property('viewport_height'); + expect(event.context.viewport_height).to.be.a('number'); + }); }); } diff --git a/test/analytics/tests/instrumented_events/from_the_browser/index.ts b/test/analytics/tests/instrumented_events/from_the_browser/index.ts index 7c6ee6ff1e2af..c69f091cc4543 100644 --- a/test/analytics/tests/instrumented_events/from_the_browser/index.ts +++ b/test/analytics/tests/instrumented_events/from_the_browser/index.ts @@ -15,5 +15,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./loaded_kibana')); loadTestFile(require.resolve('./loaded_dashboard')); loadTestFile(require.resolve('./core_context_providers')); + loadTestFile(require.resolve('./viewport_resize')); }); } diff --git a/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts b/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.ts new file mode 100644 index 0000000000000..f5e084810a375 --- /dev/null +++ b/test/analytics/tests/instrumented_events/from_the_browser/viewport_resize.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 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 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../services'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const ebtUIHelper = getService('kibana_ebt_ui'); + const browser = getService('browser'); + const { common } = getPageObjects(['common']); + + describe('Event "viewport_resize"', () => { + beforeEach(async () => { + // Navigating to `home` with the Welcome prompt because some runs were flaky + // as we handle the Welcome screen only if the login prompt pops up. + // Otherwise, it stays in the Welcome screen :/ + await common.navigateToApp('home'); + }); + + it('should emit a "viewport_resize" event when the browser is resized', async () => { + const events = await ebtUIHelper.getEvents(1, { + eventTypes: ['viewport_resize'], + withTimeoutMs: 100, + }); + expect(events.length).to.be(0); + // Resize the window + await browser.setWindowSize(500, 500); + const { height, width } = await browser.getWindowSize(); + expect(height).to.eql(500); + expect(width).to.eql(500); + + const actualInnerHeight = await browser.execute(() => window.innerHeight); + expect(actualInnerHeight <= height).to.be(true); // The address bar takes some space when not running on HEADLESS + + const [event] = await ebtUIHelper.getEvents(1, { eventTypes: ['viewport_resize'] }); + expect(event.event_type).to.eql('viewport_resize'); + expect(event.properties).to.eql({ + viewport_width: 500, + viewport_height: actualInnerHeight, + }); + + // Validating that the context is also updated + expect(event.context.viewport_width).to.be(500); + expect(event.context.viewport_height).to.be(actualInnerHeight); + }); + }); +} diff --git a/test/api_integration/apis/core/compression.ts b/test/api_integration/apis/core/compression.ts index c175fe4b9862e..c4b119692f4bb 100644 --- a/test/api_integration/apis/core/compression.ts +++ b/test/api_integration/apis/core/compression.ts @@ -12,10 +12,10 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - describe('compression', () => { + const compressionSuite = (url: string) => { it(`uses compression when there isn't a referer`, async () => { await supertest - .get('/app/kibana') + .get(url) .set('accept-encoding', 'gzip') .then((response) => { expect(response.header).to.have.property('content-encoding', 'gzip'); @@ -24,7 +24,7 @@ export default function ({ getService }: FtrProviderContext) { it(`uses compression when there is a whitelisted referer`, async () => { await supertest - .get('/app/kibana') + .get(url) .set('accept-encoding', 'gzip') .set('referer', 'https://some-host.com') .then((response) => { @@ -34,12 +34,27 @@ export default function ({ getService }: FtrProviderContext) { it(`doesn't use compression when there is a non-whitelisted referer`, async () => { await supertest - .get('/app/kibana') + .get(url) .set('accept-encoding', 'gzip') .set('referer', 'https://other.some-host.com') .then((response) => { expect(response.header).not.to.have.property('content-encoding'); }); }); + + it(`supports brotli compression`, async () => { + await supertest + .get(url) + .set('accept-encoding', 'br') + .then((response) => { + expect(response.header).to.have.property('content-encoding', 'br'); + }); + }); + }; + + describe('compression', () => { + describe('against an application page', () => { + compressionSuite('/app/kibana'); + }); }); } diff --git a/test/api_integration/config.js b/test/api_integration/config.js index 7f3f4b45298d1..ce04be64bb36e 100644 --- a/test/api_integration/config.js +++ b/test/api_integration/config.js @@ -31,6 +31,7 @@ export default async function ({ readConfigFile }) { '--elasticsearch.healthCheck.delay=3600000', '--server.xsrf.disableProtection=true', '--server.compression.referrerWhitelist=["some-host.com"]', + '--server.compression.brotli.enabled=true', `--savedObjects.maxImportExportSize=10001`, '--savedObjects.maxImportPayloadBytes=30000000', // for testing set buffer duration to 0 to immediately flush counters into saved objects. diff --git a/test/functional/apps/dashboard_elements/controls/control_group_chaining.ts b/test/functional/apps/dashboard_elements/controls/control_group_chaining.ts index a27a1a4814cfb..89a435430f9e9 100644 --- a/test/functional/apps/dashboard_elements/controls/control_group_chaining.ts +++ b/test/functional/apps/dashboard_elements/controls/control_group_chaining.ts @@ -123,6 +123,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ]); }); + it('Excluding selections in the first control will validate the second and third controls', async () => { + await dashboardControls.optionsListOpenPopover(controlIds[0]); + await dashboardControls.optionsListPopoverSetIncludeSelections(false); + await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]); + + await ensureAvailableOptionsEql(controlIds[1], ['Tiger', 'sylvester']); + await ensureAvailableOptionsEql(controlIds[2], ['meow', 'hiss']); + }); + + it('Excluding all options of first control removes all options in second and third controls', async () => { + await dashboardControls.optionsListOpenPopover(controlIds[0]); + await dashboardControls.optionsListPopoverSelectOption('cat'); + await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]); + + await dashboardControls.optionsListOpenPopover(controlIds[1]); + expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(0); + await dashboardControls.optionsListOpenPopover(controlIds[2]); + expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(0); + await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[2]); + }); + describe('Hierarchical chaining off', async () => { before(async () => { await dashboardControls.updateChainingSystem('NONE'); @@ -130,6 +151,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('Selecting an option in the first Options List will not filter the second or third controls', async () => { await dashboardControls.optionsListOpenPopover(controlIds[0]); + await dashboardControls.optionsListPopoverSetIncludeSelections(true); await dashboardControls.optionsListPopoverSelectOption('cat'); await dashboardControls.optionsListEnsurePopoverIsClosed(controlIds[0]); diff --git a/test/functional/apps/dashboard_elements/controls/options_list.ts b/test/functional/apps/dashboard_elements/controls/options_list.ts index 0c8dea528d9e4..091f893eec2cf 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list.ts @@ -385,6 +385,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await pieChart.getPieSliceCount()).to.be(2); }); + it('excluding selections has expected results', async () => { + await dashboard.clickQuickSave(); + await dashboard.waitForRenderComplete(); + + await dashboardControls.optionsListOpenPopover(controlId); + await dashboardControls.optionsListPopoverSetIncludeSelections(false); + await dashboard.waitForRenderComplete(); + + expect(await pieChart.getPieSliceCount()).to.be(5); + await dashboard.clearUnsavedChanges(); + }); + + it('including selections has expected results', async () => { + await dashboardControls.optionsListOpenPopover(controlId); + await dashboardControls.optionsListPopoverSetIncludeSelections(true); + await dashboard.waitForRenderComplete(); + + expect(await pieChart.getPieSliceCount()).to.be(2); + await dashboard.clearUnsavedChanges(); + }); + it('Can mark multiple selections invalid with Filter', async () => { await filterBar.addFilter('sound.keyword', 'is', ['hiss']); await dashboard.waitForRenderComplete(); diff --git a/test/functional/apps/discover/group1/_discover.ts b/test/functional/apps/discover/group1/_discover.ts index d6035d0a28a6e..60f2a54dd01fd 100644 --- a/test/functional/apps/discover/group1/_discover.ts +++ b/test/functional/apps/discover/group1/_discover.ts @@ -369,14 +369,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); await PageObjects.header.waitUntilLoadingHasFinished(); const dataViewId = await PageObjects.discover.getCurrentDataViewId(); - const originalUrl = await browser.getCurrentUrl(); const newUrl = originalUrl.replace(dataViewId, 'invalid-data-view-id'); await browser.get(newUrl); - await PageObjects.header.waitUntilLoadingHasFinished(); - expect(await browser.getCurrentUrl()).to.be(originalUrl); - expect(await testSubjects.exists('dscDataViewNotFoundShowDefaultWarning')).to.be(true); + await retry.try(async () => { + expect(await browser.getCurrentUrl()).to.be(originalUrl); + expect(await testSubjects.exists('dscDataViewNotFoundShowDefaultWarning')).to.be(true); + }); }); it('should show a warning and fall back to the current data view if the URL is updated to an invalid data view ID', async () => { @@ -384,14 +384,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); const originalHash = await browser.execute<[], string>('return window.location.hash'); const dataViewId = await PageObjects.discover.getCurrentDataViewId(); - const newHash = originalHash.replace(dataViewId, 'invalid-data-view-id'); await browser.execute(`window.location.hash = "${newHash}"`); await PageObjects.header.waitUntilLoadingHasFinished(); - - const currentHash = await browser.execute<[], string>('return window.location.hash'); - expect(currentHash).to.be(originalHash); - expect(await testSubjects.exists('dscDataViewNotFoundShowSavedWarning')).to.be(true); + await retry.try(async () => { + const currentHash = await browser.execute<[], string>('return window.location.hash'); + expect(currentHash).to.be(originalHash); + expect(await testSubjects.exists('dscDataViewNotFoundShowSavedWarning')).to.be(true); + }); }); }); }); diff --git a/test/functional/apps/management/_index_pattern_filter.ts b/test/functional/apps/management/_index_pattern_filter.ts index afa64c474d39d..e1e39e93cc6c5 100644 --- a/test/functional/apps/management/_index_pattern_filter.ts +++ b/test/functional/apps/management/_index_pattern_filter.ts @@ -15,7 +15,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['settings']); const esArchiver = getService('esArchiver'); - describe('index pattern filter', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/143109 + describe.skip('index pattern filter', function describeIndexTests() { before(async function () { await esArchiver.emptyKibanaIndex(); await kibanaServer.uiSettings.replace({}); diff --git a/test/functional/page_objects/dashboard_page_controls.ts b/test/functional/page_objects/dashboard_page_controls.ts index 15510a07dccb0..1e04ebb467d89 100644 --- a/test/functional/page_objects/dashboard_page_controls.ts +++ b/test/functional/page_objects/dashboard_page_controls.ts @@ -376,6 +376,19 @@ export class DashboardPageControls extends FtrService { await this.testSubjects.click(`optionsList-control-clear-all-selections`); } + public async optionsListPopoverSetIncludeSelections(include: boolean) { + this.log.debug(`exclude selections`); + await this.optionsListPopoverAssertOpen(); + + const buttonGroup = await this.testSubjects.find('optionsList__includeExcludeButtonGroup'); + await ( + await this.find.descendantDisplayedByCssSelector( + include ? '[data-text="Include"]' : '[data-text="Exclude"]', + buttonGroup + ) + ).click(); + } + /* ----------------------------------------------------------- Control editor flyout ----------------------------------------------------------- */ diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 85c93c0fc2847..44a29441d2707 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -243,6 +243,10 @@ export class DiscoverPageObject extends FtrService { return await this.testSubjects.getVisibleText('unifiedHistogramQueryHits'); } + public async getHitCountInt() { + return parseInt(await this.getHitCount(), 10); + } + public async getDocHeader() { const table = await this.getDocTable(); const docHeader = await table.getHeaders(); diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index edbd53e80d8c5..1aacde4127f37 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -659,6 +659,28 @@ export class VisualBuilderPageObject extends FtrService { await this.comboBox.setElement(fieldEl, field); } + public async setFieldForAggregateBy(field: string): Promise { + const aggregateBy = await this.testSubjects.find('tsvbAggregateBySelect'); + + await this.retry.try(async () => { + await this.comboBox.setElement(aggregateBy, field); + if (!(await this.comboBox.isOptionSelected(aggregateBy, field))) { + throw new Error(`aggregate by field - ${field} is not selected`); + } + }); + } + + public async setFunctionForAggregateFunction(func: string): Promise { + const aggregateFunction = await this.testSubjects.find('tsvbAggregateFunctionCombobox'); + + await this.retry.try(async () => { + await this.comboBox.setElement(aggregateFunction, func); + if (!(await this.comboBox.isOptionSelected(aggregateFunction, func))) { + throw new Error(`aggregate function - ${func} is not selected`); + } + }); + } + public async checkFieldForAggregationValidity(aggNth: number = 0): Promise { const fieldEl = await this.getFieldForAggregation(aggNth); diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 1c7e8a96bd1ec..c3106d19204f2 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -104,7 +104,6 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'data.search.sessions.maxUpdateRetries (number)', 'data.search.sessions.notTouchedTimeout (duration)', 'enterpriseSearch.host (string)', - 'guidedOnboarding.ui (boolean)', 'home.disableWelcomeScreen (boolean)', 'map.emsFileApiUrl (string)', 'map.emsFontLibraryUrl (string)', diff --git a/versions.json b/versions.json index 30514180e5c1e..3265499716cd1 100644 --- a/versions.json +++ b/versions.json @@ -20,7 +20,7 @@ "previousMinor": true }, { - "version": "7.17.7", + "version": "7.17.8", "branch": "7.17", "previousMajor": true } diff --git a/x-pack/examples/files_example/common/index.ts b/x-pack/examples/files_example/common/index.ts index 1586d92c4c05a..aeb807e30aadf 100644 --- a/x-pack/examples/files_example/common/index.ts +++ b/x-pack/examples/files_example/common/index.ts @@ -8,14 +8,14 @@ import type { FileKind, FileImageMetadata } from '@kbn/files-plugin/common'; export const PLUGIN_ID = 'filesExample'; -export const PLUGIN_NAME = 'filesExample'; +export const PLUGIN_NAME = 'Files example'; const httpTags = { tags: [`access:${PLUGIN_ID}`], }; export const exampleFileKind: FileKind = { - id: 'filesExample', + id: PLUGIN_ID, allowedMimeTypes: ['image/png'], http: { create: httpTags, diff --git a/x-pack/examples/files_example/kibana.json b/x-pack/examples/files_example/kibana.json index 5df1141929c41..b9cc4027a43f4 100644 --- a/x-pack/examples/files_example/kibana.json +++ b/x-pack/examples/files_example/kibana.json @@ -9,6 +9,6 @@ "description": "Example plugin integrating with files plugin", "server": true, "ui": true, - "requiredPlugins": ["files"], + "requiredPlugins": ["files", "developerExamples"], "optionalPlugins": [] } diff --git a/x-pack/examples/files_example/public/application.tsx b/x-pack/examples/files_example/public/application.tsx index 3bdbae462f6b3..0bad6975c6da0 100644 --- a/x-pack/examples/files_example/public/application.tsx +++ b/x-pack/examples/files_example/public/application.tsx @@ -22,7 +22,7 @@ export const renderApp = ( ) => { ReactDOM.render( - + , diff --git a/x-pack/examples/files_example/public/components/app.tsx b/x-pack/examples/files_example/public/components/app.tsx index cf0f4461b8b62..afdf8be1f4f6e 100644 --- a/x-pack/examples/files_example/public/components/app.tsx +++ b/x-pack/examples/files_example/public/components/app.tsx @@ -21,6 +21,7 @@ import { } from '@elastic/eui'; import { CoreStart } from '@kbn/core/public'; +import { MyFilePicker } from './file_picker'; import type { MyImageMetadata } from '../../common'; import type { FileClients } from '../types'; import { DetailsFlyout } from './details_flyout'; @@ -35,15 +36,25 @@ interface FilesExampleAppDeps { type ListResponse = FilesClientResponses['list']; export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) => { - const { data, isLoading, error, refetch } = useQuery(['files'], () => - files.example.list() + const { data, isLoading, error, refetch } = useQuery( + ['files'], + () => files.example.list(), + { refetchOnWindowFocus: false } ); const [showUploadModal, setShowUploadModal] = useState(false); + const [showFilePickerModal, setShowFilePickerModal] = useState(false); const [isDeletingFile, setIsDeletingFile] = useState(false); const [selectedItem, setSelectedItem] = useState>(); const renderToolsRight = () => { return [ + setShowFilePickerModal(true)} + isDisabled={isLoading || isDeletingFile} + iconType="eye" + > + Select a file + , setShowUploadModal(true)} isDisabled={isLoading || isDeletingFile} @@ -155,6 +166,18 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) = }} /> )} + {showFilePickerModal && ( + setShowFilePickerModal(false)} + onDone={(ids) => { + notifications.toasts.addSuccess({ + title: 'Selected files!', + text: 'IDS:' + JSON.stringify(ids, null, 2), + }); + setShowFilePickerModal(false); + }} + /> + )} ); }; diff --git a/x-pack/examples/files_example/public/components/file_picker.tsx b/x-pack/examples/files_example/public/components/file_picker.tsx new file mode 100644 index 0000000000000..3c2178b299ea2 --- /dev/null +++ b/x-pack/examples/files_example/public/components/file_picker.tsx @@ -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 React from 'react'; +import type { FunctionComponent } from 'react'; + +import { exampleFileKind } from '../../common'; + +import { FilePicker } from '../imports'; + +interface Props { + onClose: () => void; + onDone: (ids: string[]) => void; +} + +export const MyFilePicker: FunctionComponent = ({ onClose, onDone }) => { + return ; +}; diff --git a/x-pack/examples/files_example/public/components/modal.tsx b/x-pack/examples/files_example/public/components/modal.tsx index 9d323b240f416..d8289257617cf 100644 --- a/x-pack/examples/files_example/public/components/modal.tsx +++ b/x-pack/examples/files_example/public/components/modal.tsx @@ -27,8 +27,8 @@ export const Modal: FunctionComponent = ({ onDismiss, onUploaded, client diff --git a/x-pack/examples/files_example/public/imports.ts b/x-pack/examples/files_example/public/imports.ts index 7758883d0da83..a60d9cb4a6a36 100644 --- a/x-pack/examples/files_example/public/imports.ts +++ b/x-pack/examples/files_example/public/imports.ts @@ -12,5 +12,8 @@ export { UploadFile, FilesContext, ScopedFilesClient, + FilePicker, Image, } from '@kbn/files-plugin/public'; + +export type { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public'; diff --git a/x-pack/examples/files_example/public/plugin.ts b/x-pack/examples/files_example/public/plugin.ts index 98a6b6f6e4608..4906b59d4d6fc 100644 --- a/x-pack/examples/files_example/public/plugin.ts +++ b/x-pack/examples/files_example/public/plugin.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { AppNavLinkStatus } from '@kbn/core-application-browser'; import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import { PLUGIN_ID, PLUGIN_NAME, exampleFileKind, MyImageMetadata } from '../common'; import { FilesExamplePluginsStart, FilesExamplePluginsSetup } from './types'; @@ -12,12 +13,22 @@ import { FilesExamplePluginsStart, FilesExamplePluginsSetup } from './types'; export class FilesExamplePlugin implements Plugin { - public setup(core: CoreSetup, { files }: FilesExamplePluginsSetup) { + public setup( + core: CoreSetup, + { files, developerExamples }: FilesExamplePluginsSetup + ) { files.registerFileKind(exampleFileKind); + developerExamples.register({ + appId: PLUGIN_ID, + title: PLUGIN_NAME, + description: 'Example plugin for the files plugin', + }); + core.application.register({ id: PLUGIN_ID, title: PLUGIN_NAME, + navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { // Load application bundle const { renderApp } = await import('./application'); diff --git a/x-pack/examples/files_example/public/types.ts b/x-pack/examples/files_example/public/types.ts index fbc058d9aec30..0ac384055aaf3 100644 --- a/x-pack/examples/files_example/public/types.ts +++ b/x-pack/examples/files_example/public/types.ts @@ -6,10 +6,17 @@ */ import { MyImageMetadata } from '../common'; -import type { FilesSetup, FilesStart, ScopedFilesClient, FilesClient } from './imports'; +import type { + FilesSetup, + FilesStart, + ScopedFilesClient, + FilesClient, + DeveloperExamplesSetup, +} from './imports'; export interface FilesExamplePluginsSetup { files: FilesSetup; + developerExamples: DeveloperExamplesSetup; } export interface FilesExamplePluginsStart { diff --git a/x-pack/examples/files_example/tsconfig.json b/x-pack/examples/files_example/tsconfig.json index caeb25650a142..e75078a80019c 100644 --- a/x-pack/examples/files_example/tsconfig.json +++ b/x-pack/examples/files_example/tsconfig.json @@ -15,11 +15,8 @@ ], "exclude": [], "references": [ - { - "path": "../../../src/core/tsconfig.json" - }, - { - "path": "../../plugins/files/tsconfig.json" - } + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../plugins/files/tsconfig.json" }, + { "path": "../../../examples/developer_examples/tsconfig.json" } ] } diff --git a/x-pack/examples/screenshotting_example/server/plugin.ts b/x-pack/examples/screenshotting_example/server/plugin.ts index 9ca74c6e16353..16a766558ff3f 100644 --- a/x-pack/examples/screenshotting_example/server/plugin.ts +++ b/x-pack/examples/screenshotting_example/server/plugin.ts @@ -38,6 +38,7 @@ export class ScreenshottingExamplePlugin implements Plugin { ); return response.ok({ + headers: { 'content-type': 'application/json' }, body: JSON.stringify({ metrics, image: results[0]?.screenshots[0]?.data.toString('base64'), diff --git a/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts b/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts index 338f55ad754c2..7986747d34dd3 100644 --- a/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts +++ b/x-pack/packages/ml/agg_utils/src/fetch_agg_intervals.ts @@ -29,7 +29,8 @@ export const fetchAggIntervals = async ( query: estypes.QueryDslQueryContainer, fields: HistogramField[], samplerShardSize: number, - runtimeMappings?: estypes.MappingRuntimeFields + runtimeMappings?: estypes.MappingRuntimeFields, + abortSignal?: AbortSignal ): Promise => { const numericColumns = fields.filter((field) => { return field.type === KBN_FIELD_TYPES.NUMBER || field.type === KBN_FIELD_TYPES.DATE; @@ -49,16 +50,19 @@ export const fetchAggIntervals = async ( return aggs; }, {} as Record); - const body = await client.search({ - index: indexPattern, - size: 0, - body: { - query, - aggs: buildSamplerAggregation(minMaxAggs, samplerShardSize), + const body = await client.search( + { + index: indexPattern, size: 0, - ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}), + body: { + query, + aggs: buildSamplerAggregation(minMaxAggs, samplerShardSize), + size: 0, + ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}), + }, }, - }); + { signal: abortSignal, maxRetries: 0 } + ); const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); const aggregations = aggsPath.length > 0 ? get(body.aggregations, aggsPath) : body.aggregations; diff --git a/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts b/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts index a921eaeae370b..70d5f6360155d 100644 --- a/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts +++ b/x-pack/packages/ml/agg_utils/src/fetch_histograms_for_fields.ts @@ -146,7 +146,8 @@ export const fetchHistogramsForFields = async ( query: any, fields: FieldsForHistograms, samplerShardSize: number, - runtimeMappings?: estypes.MappingRuntimeFields + runtimeMappings?: estypes.MappingRuntimeFields, + abortSignal?: AbortSignal ) => { const aggIntervals = { ...(await fetchAggIntervals( @@ -155,7 +156,8 @@ export const fetchHistogramsForFields = async ( query, fields.filter((f) => !isNumericHistogramFieldWithColumnStats(f)), samplerShardSize, - runtimeMappings + runtimeMappings, + abortSignal )), ...fields.filter(isNumericHistogramFieldWithColumnStats).reduce((p, field) => { const { interval, min, max, fieldName } = field; @@ -209,7 +211,7 @@ export const fetchHistogramsForFields = async ( ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}), }, }, - { maxRetries: 0 } + { signal: abortSignal, maxRetries: 0 } ); const aggsPath = getSamplerAggregationsResponsePath(samplerShardSize); diff --git a/x-pack/plugins/actions/server/action_type_registry.test.ts b/x-pack/plugins/actions/server/action_type_registry.test.ts index bad09ce70207d..54974b4754955 100644 --- a/x-pack/plugins/actions/server/action_type_registry.test.ts +++ b/x-pack/plugins/actions/server/action_type_registry.test.ts @@ -70,7 +70,7 @@ describe('register()', () => { "actions:my-action-type": Object { "createTaskRunner": [Function], "getRetry": [Function], - "maxAttempts": 1, + "maxAttempts": 3, "title": "My action type", }, }, diff --git a/x-pack/plugins/actions/server/action_type_registry.ts b/x-pack/plugins/actions/server/action_type_registry.ts index a1466d2d40408..b19929c390bf1 100644 --- a/x-pack/plugins/actions/server/action_type_registry.ts +++ b/x-pack/plugins/actions/server/action_type_registry.ts @@ -25,6 +25,8 @@ import { ActionTypeParams, } from './types'; +export const MAX_ATTEMPTS: number = 3; + export interface ActionTypeRegistryOpts { licensing: LicensingPluginSetup; taskManager: TaskManagerSetupContract; @@ -151,7 +153,7 @@ export class ActionTypeRegistry { this.taskManager.registerTaskDefinitions({ [`actions:${actionType.id}`]: { title: actionType.name, - maxAttempts: actionType.maxAttempts || 1, + maxAttempts: actionType.maxAttempts || MAX_ATTEMPTS, getRetry(attempts: number, error: unknown) { if (error instanceof ExecutorError) { return error.retry == null ? false : error.retry; diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index 8f4c4eee61e84..19447bf8e79e5 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -16,7 +16,6 @@ import { import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects'; import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor'; import { extractSavedObjectReferences, isSavedObjectExecutionSource } from './lib'; -import { RelatedSavedObjects } from './lib/related_saved_objects'; interface CreateExecuteFunctionOptions { taskManager: TaskManagerStartContract; @@ -25,21 +24,18 @@ interface CreateExecuteFunctionOptions { preconfiguredActions: PreConfiguredAction[]; } -export interface ExecuteOptions extends Pick { +export interface ExecuteOptions + extends Pick { id: string; spaceId: string; apiKey: string | null; executionId: string; - consumer?: string; - relatedSavedObjects?: RelatedSavedObjects; } -export interface ActionTaskParams extends Pick { - actionId: string; +interface ActionTaskParams + extends Pick { apiKey: string | null; executionId: string; - consumer?: string; - relatedSavedObjects?: RelatedSavedObjects; } export interface GetConnectorsResult { @@ -176,43 +172,40 @@ export function createBulkExecutionEnqueuerFunction({ connectorIsPreconfigured[id] = isPreconfigured; }); - const actions = await Promise.all( - actionsToExecute.map(async (actionToExecute) => { - // Get saved object references from action ID and relatedSavedObjects - const { references, relatedSavedObjectWithRefs } = extractSavedObjectReferences( - actionToExecute.id, - connectorIsPreconfigured[actionToExecute.id], - actionToExecute.relatedSavedObjects - ); - const executionSourceReference = executionSourceAsSavedObjectReferences( - actionToExecute.source - ); - - const taskReferences = []; - if (executionSourceReference.references) { - taskReferences.push(...executionSourceReference.references); - } - if (references) { - taskReferences.push(...references); - } - - spaceIds[actionToExecute.id] = actionToExecute.spaceId; - - return { - type: ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, - attributes: { - actionId: actionToExecute.id, - params: actionToExecute.params, - apiKey: actionToExecute.apiKey, - executionId: actionToExecute.executionId, - consumer: actionToExecute.consumer, - relatedSavedObjects: relatedSavedObjectWithRefs, - }, - references: taskReferences, - }; - }) - ); + const actions = actionsToExecute.map((actionToExecute) => { + // Get saved object references from action ID and relatedSavedObjects + const { references, relatedSavedObjectWithRefs } = extractSavedObjectReferences( + actionToExecute.id, + connectorIsPreconfigured[actionToExecute.id], + actionToExecute.relatedSavedObjects + ); + const executionSourceReference = executionSourceAsSavedObjectReferences( + actionToExecute.source + ); + + const taskReferences = []; + if (executionSourceReference.references) { + taskReferences.push(...executionSourceReference.references); + } + if (references) { + taskReferences.push(...references); + } + spaceIds[actionToExecute.id] = actionToExecute.spaceId; + + return { + type: ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, + attributes: { + actionId: actionToExecute.id, + params: actionToExecute.params, + apiKey: actionToExecute.apiKey, + executionId: actionToExecute.executionId, + consumer: actionToExecute.consumer, + relatedSavedObjects: relatedSavedObjectWithRefs, + }, + references: taskReferences, + }; + }); const actionTaskParamsRecords: SavedObjectsBulkResponse = await unsecuredSavedObjectsClient.bulkCreate(actions); const taskInstances = actionTaskParamsRecords.saved_objects.map((so) => { diff --git a/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts b/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts new file mode 100644 index 0000000000000..ea8407b956276 --- /dev/null +++ b/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts @@ -0,0 +1,481 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import uuid from 'uuid'; +import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; +import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; +import { createBulkUnsecuredExecutionEnqueuerFunction } from './create_unsecured_execute_function'; +import { actionTypeRegistryMock } from './action_type_registry.mock'; +import { asSavedObjectExecutionSource } from './lib/action_execution_source'; + +const mockTaskManager = taskManagerMock.createStart(); +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); + +beforeEach(() => jest.resetAllMocks()); + +describe('bulkExecute()', () => { + test('schedules the actions with all given parameters with a preconfigured connector', async () => { + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + + internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({ + saved_objects: [ + { + id: '234', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [], + }, + { + id: '345', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [], + }, + ], + }); + await executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + }, + { + id: '123', + params: { baz: true }, + }, + ]); + expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); + expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "params": Object { + "actionTaskParamsId": "234", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + Object { + "params": Object { + "actionTaskParamsId": "345", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + ], + ] + `); + + expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([ + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: false }, + apiKey: null, + }, + references: [], + }, + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: true }, + apiKey: null, + }, + references: [], + }, + ]); + }); + + test('schedules the actions with all given parameters with a preconfigured connector and source specified', async () => { + const sourceUuid = uuid.v4(); + const source = { type: 'alert', id: sourceUuid }; + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + + internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({ + saved_objects: [ + { + id: '234', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [ + { + id: sourceUuid, + name: 'source', + type: 'alert', + }, + ], + }, + { + id: '345', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [], + }, + ], + }); + await executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + source: asSavedObjectExecutionSource(source), + }, + { + id: '123', + params: { baz: true }, + }, + ]); + expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); + expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "params": Object { + "actionTaskParamsId": "234", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + Object { + "params": Object { + "actionTaskParamsId": "345", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + ], + ] + `); + + expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([ + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: false }, + apiKey: null, + }, + references: [ + { + id: sourceUuid, + name: 'source', + type: 'alert', + }, + ], + }, + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: true }, + apiKey: null, + }, + references: [], + }, + ]); + }); + + test('schedules the actions with all given parameters with a preconfigured connector and relatedSavedObjects specified', async () => { + const sourceUuid = uuid.v4(); + const source = { type: 'alert', id: sourceUuid }; + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + + internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({ + saved_objects: [ + { + id: '234', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [ + { + id: sourceUuid, + name: 'source', + type: 'alert', + }, + ], + }, + { + id: '345', + type: 'action_task_params', + attributes: { + actionId: '123', + }, + references: [ + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + }, + ], + }); + await executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + source: asSavedObjectExecutionSource(source), + }, + { + id: '123', + params: { baz: true }, + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + }, + ], + }, + ]); + expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); + expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "params": Object { + "actionTaskParamsId": "234", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + Object { + "params": Object { + "actionTaskParamsId": "345", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:.email", + }, + ], + ] + `); + + expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([ + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: false }, + apiKey: null, + }, + references: [ + { + id: sourceUuid, + name: 'source', + type: 'alert', + }, + ], + }, + { + type: 'action_task_params', + attributes: { + actionId: '123', + params: { baz: true }, + apiKey: null, + relatedSavedObjects: [ + { + id: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + }, + ], + }, + references: [ + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + }, + ]); + }); + + test('throws when scheduling action using non preconfigured connector', async () => { + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + await expect( + executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + }, + { + id: 'not-preconfigured', + params: { baz: true }, + }, + ]) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"not-preconfigured are not preconfigured connectors and can't be scheduled for unsecured actions execution"` + ); + }); + + test('throws when connector type is not enabled', async () => { + const mockedConnectorTypeRegistry = actionTypeRegistryMock.create(); + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: mockedConnectorTypeRegistry, + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + mockedConnectorTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => { + throw new Error('Fail'); + }); + + await expect( + executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + }, + { + id: '123', + params: { baz: true }, + }, + ]) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`); + }); + + test('throws when scheduling action using non allow-listed preconfigured connector', async () => { + const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + connectorTypeRegistry: actionTypeRegistryMock.create(), + preconfiguredConnectors: [ + { + id: '123', + actionTypeId: '.email', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + { + id: '456', + actionTypeId: 'not-in-allowlist', + config: {}, + isPreconfigured: true, + isDeprecated: false, + name: 'x', + secrets: {}, + }, + ], + }); + await expect( + executeFn(internalSavedObjectsRepository, [ + { + id: '123', + params: { baz: false }, + }, + { + id: '456', + params: { baz: true }, + }, + ]) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"not-in-allowlist actions cannot be scheduled for unsecured actions execution"` + ); + }); +}); diff --git a/x-pack/plugins/actions/server/create_unsecured_execute_function.ts b/x-pack/plugins/actions/server/create_unsecured_execute_function.ts new file mode 100644 index 0000000000000..4670601ecff83 --- /dev/null +++ b/x-pack/plugins/actions/server/create_unsecured_execute_function.ts @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ISavedObjectsRepository, SavedObjectsBulkResponse } from '@kbn/core/server'; +import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; +import { + ActionTypeRegistryContract as ConnectorTypeRegistryContract, + PreConfiguredAction as PreconfiguredConnector, +} from './types'; +import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects'; +import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor'; +import { extractSavedObjectReferences, isSavedObjectExecutionSource } from './lib'; + +// This allowlist should only contain connector types that don't require API keys for +// execution. +const ALLOWED_CONNECTOR_TYPE_IDS = ['.email']; +interface CreateBulkUnsecuredExecuteFunctionOptions { + taskManager: TaskManagerStartContract; + connectorTypeRegistry: ConnectorTypeRegistryContract; + preconfiguredConnectors: PreconfiguredConnector[]; +} + +export interface ExecuteOptions + extends Pick { + id: string; +} + +interface ActionTaskParams + extends Pick { + apiKey: string | null; +} + +export type BulkUnsecuredExecutionEnqueuer = ( + internalSavedObjectsRepository: ISavedObjectsRepository, + actionsToExectute: ExecuteOptions[] +) => Promise; + +export function createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager, + connectorTypeRegistry, + preconfiguredConnectors, +}: CreateBulkUnsecuredExecuteFunctionOptions): BulkUnsecuredExecutionEnqueuer { + return async function execute( + internalSavedObjectsRepository: ISavedObjectsRepository, + actionsToExecute: ExecuteOptions[] + ) { + const connectorTypeIds: Record = {}; + const connectorIds = [...new Set(actionsToExecute.map((action) => action.id))]; + + const notPreconfiguredConnectors = connectorIds.filter( + (connectorId) => + preconfiguredConnectors.find((connector) => connector.id === connectorId) == null + ); + + if (notPreconfiguredConnectors.length > 0) { + throw new Error( + `${notPreconfiguredConnectors.join( + ',' + )} are not preconfigured connectors and can't be scheduled for unsecured actions execution` + ); + } + + const connectors: PreconfiguredConnector[] = connectorIds + .map((connectorId) => + preconfiguredConnectors.find((pConnector) => pConnector.id === connectorId) + ) + .filter(Boolean) as PreconfiguredConnector[]; + + connectors.forEach((connector) => { + const { id, actionTypeId } = connector; + if (!connectorTypeRegistry.isActionExecutable(id, actionTypeId, { notifyUsage: true })) { + connectorTypeRegistry.ensureActionTypeEnabled(actionTypeId); + } + + if (!ALLOWED_CONNECTOR_TYPE_IDS.includes(actionTypeId)) { + throw new Error( + `${actionTypeId} actions cannot be scheduled for unsecured actions execution` + ); + } + + connectorTypeIds[id] = actionTypeId; + }); + + const actions = actionsToExecute.map((actionToExecute) => { + // Get saved object references from action ID and relatedSavedObjects + const { references, relatedSavedObjectWithRefs } = extractSavedObjectReferences( + actionToExecute.id, + true, + actionToExecute.relatedSavedObjects + ); + const executionSourceReference = executionSourceAsSavedObjectReferences( + actionToExecute.source + ); + + const taskReferences = []; + if (executionSourceReference.references) { + taskReferences.push(...executionSourceReference.references); + } + if (references) { + taskReferences.push(...references); + } + + return { + type: ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, + attributes: { + actionId: actionToExecute.id, + params: actionToExecute.params, + apiKey: null, + relatedSavedObjects: relatedSavedObjectWithRefs, + }, + references: taskReferences, + }; + }); + const actionTaskParamsRecords: SavedObjectsBulkResponse = + await internalSavedObjectsRepository.bulkCreate(actions); + + const taskInstances = actionTaskParamsRecords.saved_objects.map((so) => { + const actionId = so.attributes.actionId; + return { + taskType: `actions:${connectorTypeIds[actionId]}`, + params: { + spaceId: 'default', + actionTaskParamsId: so.id, + }, + state: {}, + scope: ['actions'], + }; + }); + await taskManager.bulkSchedule(taskInstances); + }; +} + +function executionSourceAsSavedObjectReferences(executionSource: ActionExecutorOptions['source']) { + return isSavedObjectExecutionSource(executionSource) + ? { + references: [ + { + name: 'source', + ...executionSource.source, + }, + ], + } + : {}; +} diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index 1c7a66978ffb3..2713ee17463e4 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -12,6 +12,8 @@ import { configSchema, ActionsConfig, CustomHostSettings } from './config'; import { ActionsClient as ActionsClientClass } from './actions_client'; import { ActionsAuthorization as ActionsAuthorizationClass } from './authorization/actions_authorization'; +export type { IUnsecuredActionsClient } from './unsecured_actions_client/unsecured_actions_client'; +export { UnsecuredActionsClient } from './unsecured_actions_client/unsecured_actions_client'; export type ActionsClient = PublicMethodsOf; export type ActionsAuthorization = PublicMethodsOf; diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index 35791ffa01f23..4fde645fb367e 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -47,7 +47,21 @@ actionExecutor.initialize({ actionTypeRegistry, encryptedSavedObjectsClient, eventLogger, - preconfiguredActions: [], + preconfiguredActions: [ + { + id: 'preconfigured', + name: 'Preconfigured', + actionTypeId: 'test', + config: { + bar: 'preconfigured', + }, + secrets: { + apiKey: 'abc', + }, + isPreconfigured: true, + isDeprecated: false, + }, + ], }); beforeEach(() => { @@ -183,6 +197,107 @@ test('successfully executes', async () => { `); }); +test('successfully executes with preconfigured connector', async () => { + const actionType: jest.Mocked = { + id: 'test', + name: 'Test', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + executor: jest.fn(), + }; + + actionTypeRegistry.get.mockReturnValueOnce(actionType); + await actionExecutor.execute({ ...executeParams, actionId: 'preconfigured' }); + + expect(actionsClient.get).not.toHaveBeenCalled(); + expect(encryptedSavedObjectsClient.getDecryptedAsInternalUser).not.toHaveBeenCalled(); + + expect(actionTypeRegistry.get).toHaveBeenCalledWith('test'); + expect(actionTypeRegistry.isActionExecutable).toHaveBeenCalledWith('preconfigured', 'test', { + notifyUsage: true, + }); + + expect(actionType.executor).toHaveBeenCalledWith({ + actionId: 'preconfigured', + services: expect.anything(), + config: { + bar: 'preconfigured', + }, + secrets: { + apiKey: 'abc', + }, + params: { foo: true }, + logger: loggerMock, + }); + + expect(loggerMock.debug).toBeCalledWith('executing action test:preconfigured: Preconfigured'); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute-start", + "kind": "action", + }, + "kibana": Object { + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "preconfigured", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action started: test:preconfigured: Preconfigured", + }, + ], + Array [ + Object { + "event": Object { + "action": "execute", + "kind": "action", + "outcome": "success", + }, + "kibana": Object { + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "preconfigured", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action executed: test:preconfigured: Preconfigured", + }, + ], + ] + `); +}); + test('successfully executes as a task', async () => { const actionType: jest.Mocked = { id: 'test', @@ -509,6 +624,132 @@ test('throws an error when passing isESOCanEncrypt with value of false', async ( ); }); +test('should not throw error if action is preconfigured and isESOCanEncrypt is false', async () => { + const customActionExecutor = new ActionExecutor({ isESOCanEncrypt: false }); + customActionExecutor.initialize({ + logger: loggingSystemMock.create().get(), + spaces: spacesMock, + getActionsClientWithRequest, + getServices: () => services, + actionTypeRegistry, + encryptedSavedObjectsClient, + eventLogger: eventLoggerMock.create(), + preconfiguredActions: [ + { + id: 'preconfigured', + name: 'Preconfigured', + actionTypeId: 'test', + config: { + bar: 'preconfigured', + }, + secrets: { + apiKey: 'abc', + }, + isPreconfigured: true, + isDeprecated: false, + }, + ], + }); + const actionType: jest.Mocked = { + id: 'test', + name: 'Test', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + executor: jest.fn(), + }; + + actionTypeRegistry.get.mockReturnValueOnce(actionType); + await actionExecutor.execute({ ...executeParams, actionId: 'preconfigured' }); + + expect(actionsClient.get).not.toHaveBeenCalled(); + expect(encryptedSavedObjectsClient.getDecryptedAsInternalUser).not.toHaveBeenCalled(); + + expect(actionTypeRegistry.get).toHaveBeenCalledWith('test'); + expect(actionTypeRegistry.isActionExecutable).toHaveBeenCalledWith('preconfigured', 'test', { + notifyUsage: true, + }); + + expect(actionType.executor).toHaveBeenCalledWith({ + actionId: 'preconfigured', + services: expect.anything(), + config: { + bar: 'preconfigured', + }, + secrets: { + apiKey: 'abc', + }, + params: { foo: true }, + logger: loggerMock, + }); + + expect(loggerMock.debug).toBeCalledWith('executing action test:preconfigured: Preconfigured'); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute-start", + "kind": "action", + }, + "kibana": Object { + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "preconfigured", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action started: test:preconfigured: Preconfigured", + }, + ], + Array [ + Object { + "event": Object { + "action": "execute", + "kind": "action", + "outcome": "success", + }, + "kibana": Object { + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "preconfigured", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action executed: test:preconfigured: Preconfigured", + }, + ], + ] + `); +}); + test('does not log warning when alert executor succeeds', async () => { const executorMock = setupActionExecutorMock(); executorMock.mockResolvedValue({ @@ -540,7 +781,7 @@ test('logs a warning and error when alert executor throws an error', async () => executorMock.mockRejectedValue(err); await actionExecutor.execute(executeParams); expect(loggerMock.warn).toBeCalledWith( - 'action execution failure: test:1: action-1: an error occurred while running the action: this action execution is intended to fail' + 'action execution failure: test:1: action-1: an error occurred while running the action: this action execution is intended to fail; retry: true' ); expect(loggerMock.error).toBeCalledWith(err, { error: { stack_trace: 'foo error\n stack 1\n stack 2\n stack 3' }, diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index 26d0a55b07dc6..42e5c8c8e1a99 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -105,12 +105,6 @@ export class ActionExecutor { throw new Error('ActionExecutor not initialized'); } - if (!this.isESOCanEncrypt) { - throw new Error( - `Unable to execute action because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.` - ); - } - return withSpan( { name: `execute_action`, @@ -135,11 +129,14 @@ export class ActionExecutor { const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {}; const actionInfo = await getActionInfoInternal( - await getActionsClientWithRequest(request, source), + getActionsClientWithRequest, + request, + this.isESOCanEncrypt, encryptedSavedObjectsClient, preconfiguredActions, actionId, - namespace.namespace + namespace.namespace, + source ); const { actionTypeId, name, config, secrets } = actionInfo; @@ -242,7 +239,7 @@ export class ActionExecutor { message: 'an error occurred while running the action', serviceMessage: err.message, error: err, - retry: false, + retry: true, }; } } @@ -321,11 +318,14 @@ export class ActionExecutor { const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {}; if (!this.actionInfo || this.actionInfo.actionId !== actionId) { this.actionInfo = await getActionInfoInternal( - await getActionsClientWithRequest(request, source), + getActionsClientWithRequest, + request, + this.isESOCanEncrypt, encryptedSavedObjectsClient, preconfiguredActions, actionId, - namespace.namespace + namespace.namespace, + source ); } const task = taskInfo @@ -371,12 +371,18 @@ interface ActionInfo { actionId: string; } -async function getActionInfoInternal( - actionsClient: PublicMethodsOf, +async function getActionInfoInternal( + getActionsClientWithRequest: ( + request: KibanaRequest, + authorizationContext?: ActionExecutionSource + ) => Promise>, + request: KibanaRequest, + isESOCanEncrypt: boolean, encryptedSavedObjectsClient: EncryptedSavedObjectsClient, preconfiguredActions: PreConfiguredAction[], actionId: string, - namespace: string | undefined + namespace: string | undefined, + source?: ActionExecutionSource ): Promise { // check to see if it's a pre-configured action first const pcAction = preconfiguredActions.find( @@ -392,6 +398,14 @@ async function getActionInfoInternal( }; } + if (!isESOCanEncrypt) { + throw new Error( + `Unable to execute action because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.` + ); + } + + const actionsClient = await getActionsClientWithRequest(request, source); + // if not pre-configured action, should be a saved object // ensure user can read the action before processing const { actionTypeId, config, name } = await actionsClient.get({ id: actionId }); diff --git a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts index 72cbda1312b9a..69eca915cc721 100644 --- a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts +++ b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts @@ -109,6 +109,43 @@ describe('createActionEventLogRecordObject', () => { }); }); + test('created action event "execute" with no kibana.alert.rule fields', async () => { + expect( + createActionEventLogRecordObject({ + actionId: '1', + name: 'test name', + action: 'execute', + message: 'action execution start', + namespace: 'default', + savedObjects: [ + { + id: '2', + type: 'action', + typeId: '.email', + relation: 'primary', + }, + ], + }) + ).toStrictEqual({ + event: { + action: 'execute', + kind: 'action', + }, + kibana: { + saved_objects: [ + { + id: '2', + namespace: 'default', + rel: 'primary', + type: 'action', + type_id: '.email', + }, + ], + }, + message: 'action execution start', + }); + }); + test('created action event "execute-timeout"', async () => { expect( createActionEventLogRecordObject({ diff --git a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts index 5d556398dc668..2632ead26a477 100644 --- a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts +++ b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { set } from 'lodash'; +import { isEmpty, set } from 'lodash'; import { IEvent, SAVED_OBJECT_REL_PRIMARY } from '@kbn/event-log-plugin/server'; import { RelatedSavedObjects } from './related_saved_objects'; @@ -38,6 +38,17 @@ export function createActionEventLogRecordObject(params: CreateActionEventLogRec const { action, message, task, namespace, executionId, spaceId, consumer, relatedSavedObjects } = params; + const kibanaAlertRule = { + ...(consumer ? { consumer } : {}), + ...(executionId + ? { + execution: { + uuid: executionId, + }, + } + : {}), + }; + const event: Event = { ...(params.timestamp ? { '@timestamp': params.timestamp } : {}), event: { @@ -45,18 +56,7 @@ export function createActionEventLogRecordObject(params: CreateActionEventLogRec kind: 'action', }, kibana: { - alert: { - rule: { - ...(consumer ? { consumer } : {}), - ...(executionId - ? { - execution: { - uuid: executionId, - }, - } - : {}), - }, - }, + ...(!isEmpty(kibanaAlertRule) ? { alert: { rule: kibanaAlertRule } } : {}), saved_objects: params.savedObjects.map((so) => ({ ...(so.relation ? { rel: so.relation } : {}), type: so.type, diff --git a/x-pack/plugins/actions/server/lib/get_custom_agents.test.ts b/x-pack/plugins/actions/server/lib/get_custom_agents.test.ts index 9bf1e26f1d374..7d61e4b4c6d43 100644 --- a/x-pack/plugins/actions/server/lib/get_custom_agents.test.ts +++ b/x-pack/plugins/actions/server/lib/get_custom_agents.test.ts @@ -6,7 +6,7 @@ */ import { Agent as HttpsAgent } from 'https'; -import HttpProxyAgent from 'http-proxy-agent'; +import { HttpProxyAgent } from 'http-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { Logger } from '@kbn/core/server'; import { getCustomAgents } from './get_custom_agents'; diff --git a/x-pack/plugins/actions/server/lib/get_custom_agents.ts b/x-pack/plugins/actions/server/lib/get_custom_agents.ts index 49eca54afcde7..220abeb6cc928 100644 --- a/x-pack/plugins/actions/server/lib/get_custom_agents.ts +++ b/x-pack/plugins/actions/server/lib/get_custom_agents.ts @@ -7,7 +7,7 @@ import { Agent as HttpAgent } from 'http'; import { Agent as HttpsAgent, AgentOptions } from 'https'; -import HttpProxyAgent from 'http-proxy-agent'; +import { HttpProxyAgent } from 'http-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { Logger } from '@kbn/core/server'; import { ActionsConfigurationUtilities } from '../actions_config'; diff --git a/x-pack/plugins/actions/server/mocks.ts b/x-pack/plugins/actions/server/mocks.ts index 3b8155818452f..4d5846de9528f 100644 --- a/x-pack/plugins/actions/server/mocks.ts +++ b/x-pack/plugins/actions/server/mocks.ts @@ -17,6 +17,7 @@ import { PluginSetupContract, PluginStartContract, renderActionParameterTemplate import { Services } from './types'; import { actionsAuthorizationMock } from './authorization/actions_authorization.mock'; import { ConnectorTokenClient } from './lib/connector_token_client'; +import { unsecuredActionsClientMock } from './unsecured_actions_client/unsecured_actions_client.mock'; export { actionsAuthorizationMock }; export { actionsClientMock }; const logger = loggingSystemMock.create().get() as jest.Mocked; @@ -38,6 +39,7 @@ const createStartMock = () => { isActionTypeEnabled: jest.fn(), isActionExecutable: jest.fn(), getActionsClientWithRequest: jest.fn().mockResolvedValue(actionsClientMock.create()), + getUnsecuredActionsClient: jest.fn().mockResolvedValue(unsecuredActionsClientMock.create()), getActionsAuthorizationWithRequest: jest .fn() .mockReturnValue(actionsAuthorizationMock.create()), diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index fa70e0dc71354..e24ac8247bfd8 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -43,7 +43,7 @@ import { import { ActionsConfig, getValidatedConfig } from './config'; import { resolveCustomHosts } from './lib/custom_host_settings'; import { ActionsClient } from './actions_client'; -import { ActionTypeRegistry } from './action_type_registry'; +import { ActionTypeRegistry, MAX_ATTEMPTS } from './action_type_registry'; import { createExecutionEnqueuerFunction, createEphemeralExecutionEnqueuerFunction, @@ -101,6 +101,11 @@ import { createSubActionConnectorFramework } from './sub_action_framework'; import { IServiceAbstract, SubActionConnectorType } from './sub_action_framework/types'; import { SubActionConnector } from './sub_action_framework/sub_action_connector'; import { CaseConnector } from './sub_action_framework/case'; +import { + IUnsecuredActionsClient, + UnsecuredActionsClient, +} from './unsecured_actions_client/unsecured_actions_client'; +import { createBulkUnsecuredExecutionEnqueuerFunction } from './create_unsecured_execute_function'; export interface PluginSetupContract { registerType< @@ -138,6 +143,8 @@ export interface PluginStartContract { preconfiguredActions: PreConfiguredAction[]; + getUnsecuredActionsClient(): IUnsecuredActionsClient; + renderActionParameterTemplates( actionTypeId: string, actionId: string, @@ -351,6 +358,7 @@ export class ActionsPlugin implements Plugin ) => { ensureSufficientLicense(actionType); + actionType.maxAttempts = actionType.maxAttempts ?? MAX_ATTEMPTS; actionTypeRegistry.register(actionType); }, registerSubActionConnectorType: < @@ -452,6 +460,21 @@ export class ActionsPlugin implements Plugin { + const internalSavedObjectsRepository = core.savedObjects.createInternalRepository([ + ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, + ]); + + return new UnsecuredActionsClient({ + internalSavedObjectsRepository, + executionEnqueuer: createBulkUnsecuredExecutionEnqueuerFunction({ + taskManager: plugins.taskManager, + connectorTypeRegistry: actionTypeRegistry!, + preconfiguredConnectors: preconfiguredActions, + }), + }); + }; + // Ensure the public API cannot be used to circumvent authorization // using our legacy exemption mechanism by passing in a legacy SO // as authorizationContext which would then set a Legacy AuthorizationMode @@ -532,6 +555,7 @@ export class ActionsPlugin implements Plugin renderActionParameterTemplates(actionTypeRegistry, ...args), diff --git a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.mock.ts b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.mock.ts new file mode 100644 index 0000000000000..eb8d4de53e7f3 --- /dev/null +++ b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.mock.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 { IUnsecuredActionsClient } from './unsecured_actions_client'; + +export type UnsecuredActionsClientMock = jest.Mocked; + +const createUnsecuredActionsClientMock = () => { + const mocked: UnsecuredActionsClientMock = { + bulkEnqueueExecution: jest.fn(), + }; + return mocked; +}; + +export const unsecuredActionsClientMock = { + create: createUnsecuredActionsClientMock, +}; diff --git a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.test.ts b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.test.ts new file mode 100644 index 0000000000000..c863e943b8dc0 --- /dev/null +++ b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.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 { UnsecuredActionsClient } from './unsecured_actions_client'; +import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; + +const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); +const executionEnqueuer = jest.fn(); + +let unsecuredActionsClient: UnsecuredActionsClient; + +beforeEach(() => { + jest.resetAllMocks(); + unsecuredActionsClient = new UnsecuredActionsClient({ + internalSavedObjectsRepository, + executionEnqueuer, + }); +}); + +describe('bulkEnqueueExecution()', () => { + test('throws error when enqueuing execution with not allowed requester id', async () => { + const opts = [ + { + id: 'preconfigured1', + params: {}, + executionId: '123abc', + }, + { + id: 'preconfigured2', + params: {}, + executionId: '456def', + }, + ]; + await expect( + unsecuredActionsClient.bulkEnqueueExecution('badId', opts) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"\\"badId\\" feature is not allow-listed for UnsecuredActionsClient access."` + ); + }); + + test('calls the executionEnqueuer with the appropriate parameters', async () => { + const opts = [ + { + id: 'preconfigured1', + params: {}, + executionId: '123abc', + }, + { + id: 'preconfigured2', + params: {}, + executionId: '456def', + }, + ]; + await expect( + unsecuredActionsClient.bulkEnqueueExecution('notifications', opts) + ).resolves.toMatchInlineSnapshot(`undefined`); + + expect(executionEnqueuer).toHaveBeenCalledWith(internalSavedObjectsRepository, opts); + }); +}); diff --git a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts new file mode 100644 index 0000000000000..333490389013a --- /dev/null +++ b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.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 { ISavedObjectsRepository } from '@kbn/core/server'; +import { + BulkUnsecuredExecutionEnqueuer, + ExecuteOptions, +} from '../create_unsecured_execute_function'; + +// allowlist for features wanting access to the unsecured actions client +// which allows actions to be enqueued for execution without a user request +const ALLOWED_REQUESTER_IDS = [ + 'notifications', + // For functional testing + 'functional_tester', +]; + +export interface UnsecuredActionsClientOpts { + internalSavedObjectsRepository: ISavedObjectsRepository; + executionEnqueuer: BulkUnsecuredExecutionEnqueuer; +} + +export interface IUnsecuredActionsClient { + bulkEnqueueExecution: (requesterId: string, actionsToExecute: ExecuteOptions[]) => Promise; +} + +export class UnsecuredActionsClient { + private readonly internalSavedObjectsRepository: ISavedObjectsRepository; + private readonly executionEnqueuer: BulkUnsecuredExecutionEnqueuer; + + constructor(params: UnsecuredActionsClientOpts) { + this.executionEnqueuer = params.executionEnqueuer; + this.internalSavedObjectsRepository = params.internalSavedObjectsRepository; + } + + public async bulkEnqueueExecution( + requesterId: string, + actionsToExecute: ExecuteOptions[] + ): Promise { + // Check that requesterId is allowed + if (!ALLOWED_REQUESTER_IDS.includes(requesterId)) { + throw new Error( + `"${requesterId}" feature is not allow-listed for UnsecuredActionsClient access.` + ); + } + return this.executionEnqueuer(this.internalSavedObjectsRepository, actionsToExecute); + } +} diff --git a/x-pack/plugins/aiops/server/lib/is_request_aborted_error.test.ts b/x-pack/plugins/aiops/server/lib/is_request_aborted_error.test.ts new file mode 100644 index 0000000000000..ee9725dedda4b --- /dev/null +++ b/x-pack/plugins/aiops/server/lib/is_request_aborted_error.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isRequestAbortedError } from './is_request_aborted_error'; + +describe('isRequestAbortedError', () => { + it('returns false for a string', () => { + expect(isRequestAbortedError('the-error')).toBe(false); + }); + it('returns false for a an object without a name field', () => { + expect(isRequestAbortedError({ error: 'the-error' })).toBe(false); + }); + it(`returns false for a an object with a name field other than 'RequestAbortedError'`, () => { + expect(isRequestAbortedError({ name: 'the-error' })).toBe(false); + }); + it(`returns true for a an object with a name field that contains 'RequestAbortedError'`, () => { + expect(isRequestAbortedError({ name: 'RequestAbortedError' })).toBe(true); + }); +}); diff --git a/x-pack/plugins/aiops/server/lib/is_request_aborted_error.ts b/x-pack/plugins/aiops/server/lib/is_request_aborted_error.ts new file mode 100644 index 0000000000000..b80256dcd8639 --- /dev/null +++ b/x-pack/plugins/aiops/server/lib/is_request_aborted_error.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; + +interface RequestAbortedError extends Error { + name: 'RequestAbortedError'; +} + +export function isRequestAbortedError(arg: unknown): arg is RequestAbortedError { + return isPopulatedObject(arg, ['name']) && arg.name === 'RequestAbortedError'; +} diff --git a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts index c61e1915f68f7..2468df9df8237 100644 --- a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts +++ b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts @@ -34,6 +34,7 @@ import { } from '../../common/api/explain_log_rate_spikes'; import { API_ENDPOINT } from '../../common/api'; +import { isRequestAbortedError } from '../lib/is_request_aborted_error'; import type { AiopsLicense } from '../types'; import { fetchChangePointPValues } from './queries/fetch_change_point_p_values'; @@ -92,6 +93,7 @@ export const defineExplainLogRateSpikesRoute = ( const client = (await context.core).elasticsearch.client.asCurrentUser; const controller = new AbortController(); + const abortSignal = controller.signal; let isRunning = false; let loaded = 0; @@ -129,9 +131,13 @@ export const defineExplainLogRateSpikesRoute = ( } function end() { - isRunning = false; - logDebugMessage('Ending analysis.'); - streamEnd(); + if (isRunning) { + isRunning = false; + logDebugMessage('Ending analysis.'); + streamEnd(); + } else { + logDebugMessage('end() was called again with isRunning already being false.'); + } } function endWithUpdatedLoadingState() { @@ -178,10 +184,12 @@ export const defineExplainLogRateSpikesRoute = ( let fieldCandidates: Awaited>; try { - fieldCandidates = await fetchFieldCandidates(client, request.body); + fieldCandidates = await fetchFieldCandidates(client, request.body, abortSignal); } catch (e) { - logger.error(`Failed to fetch field candidates, got: \n${e.toString()}`); - pushError(`Failed to fetch field candidates.`); + if (!isRequestAbortedError(e)) { + logger.error(`Failed to fetch field candidates, got: \n${e.toString()}`); + pushError(`Failed to fetch field candidates.`); + } end(); return; } @@ -208,16 +216,20 @@ export const defineExplainLogRateSpikesRoute = ( if (fieldCandidates.length === 0) { endWithUpdatedLoadingState(); } else if (shouldStop) { + logDebugMessage('shouldStop after fetching field candidates.'); end(); return; } const changePoints: ChangePoint[] = []; const fieldsToSample = new Set(); - const chunkSize = 10; + + // Don't use more than 10 here otherwise Kibana will emit an error + // regarding a limit of abort signal listeners of more than 10. + const CHUNK_SIZE = 10; let chunkCount = 0; - const fieldCandidatesChunks = chunk(fieldCandidates, chunkSize); + const fieldCandidatesChunks = chunk(fieldCandidates, CHUNK_SIZE); logDebugMessage('Fetch p-values.'); @@ -233,16 +245,18 @@ export const defineExplainLogRateSpikesRoute = ( request.body, fieldCandidatesChunk, logger, - pushError + pushError, + abortSignal ); } catch (e) { - logger.error( - `Failed to fetch p-values for ${JSON.stringify( - fieldCandidatesChunk - )}, got: \n${e.toString()}` - ); - pushError(`Failed to fetch p-values for ${JSON.stringify(fieldCandidatesChunk)}.`); - // Still continue the analysis even if chunks of p-value queries fail. + if (!isRequestAbortedError(e)) { + logger.error( + `Failed to fetch p-values for ${JSON.stringify( + fieldCandidatesChunk + )}, got: \n${e.toString()}` + ); + pushError(`Failed to fetch p-values for ${JSON.stringify(fieldCandidatesChunk)}.`); + } // Still continue the analysis even if chunks of p-value queries fail. continue; } @@ -267,7 +281,7 @@ export const defineExplainLogRateSpikesRoute = ( defaultMessage: 'Identified {fieldValuePairsCount, plural, one {# significant field/value pair} other {# significant field/value pairs}}.', values: { - fieldValuePairsCount: changePoints?.length ?? 0, + fieldValuePairsCount: changePoints.length, }, } ), @@ -276,13 +290,12 @@ export const defineExplainLogRateSpikesRoute = ( if (shouldStop) { logDebugMessage('shouldStop fetching p-values.'); - end(); return; } } - if (changePoints?.length === 0) { + if (changePoints.length === 0) { logDebugMessage('Stopping analysis, did not find change points.'); endWithUpdatedLoadingState(); return; @@ -305,12 +318,15 @@ export const defineExplainLogRateSpikesRoute = ( histogramFields, // samplerShardSize -1, - undefined + undefined, + abortSignal )) as [NumericChartData] )[0]; } catch (e) { - logger.error(`Failed to fetch the overall histogram data, got: \n${e.toString()}`); - pushError(`Failed to fetch overall histogram data.`); + if (!isRequestAbortedError(e)) { + logger.error(`Failed to fetch the overall histogram data, got: \n${e.toString()}`); + pushError(`Failed to fetch overall histogram data.`); + } // Still continue the analysis even if loading the overall histogram fails. } @@ -329,6 +345,12 @@ export const defineExplainLogRateSpikesRoute = ( ); } + if (shouldStop) { + logDebugMessage('shouldStop after fetching overall histogram.'); + end(); + return; + } + if (groupingEnabled) { logDebugMessage('Group results.'); @@ -374,9 +396,16 @@ export const defineExplainLogRateSpikesRoute = ( request.body.deviationMin, request.body.deviationMax, logger, - pushError + pushError, + abortSignal ); + if (shouldStop) { + logDebugMessage('shouldStop after fetching frequent_items.'); + end(); + return; + } + if (fields.length > 0 && df.length > 0) { // The way the `frequent_items` aggregations works could return item sets that include // field/value pairs that are not part of the original list of significant change points. @@ -517,172 +546,208 @@ export const defineExplainLogRateSpikesRoute = ( pushHistogramDataLoadingState(); - logDebugMessage('Fetch group histograms.'); + if (shouldStop) { + logDebugMessage('shouldStop after grouping.'); + end(); + return; + } - await asyncForEach(changePointGroups, async (cpg) => { - if (overallTimeSeries !== undefined) { - const histogramQuery = { - bool: { - filter: cpg.group.map((d) => ({ - term: { [d.fieldName]: d.fieldValue }, - })), - }, - }; + logDebugMessage(`Fetch ${changePointGroups.length} group histograms.`); - let cpgTimeSeries: NumericChartData; - try { - cpgTimeSeries = ( - (await fetchHistogramsForFields( - client, - request.body.index, - histogramQuery, - // fields - [ - { - fieldName: request.body.timeFieldName, - type: KBN_FIELD_TYPES.DATE, - interval: overallTimeSeries.interval, - min: overallTimeSeries.stats[0], - max: overallTimeSeries.stats[1], - }, - ], - // samplerShardSize - -1, - undefined - )) as [NumericChartData] - )[0]; - } catch (e) { - logger.error( - `Failed to fetch the histogram data for group #${ - cpg.id - }, got: \n${e.toString()}` - ); - pushError(`Failed to fetch the histogram data for group #${cpg.id}.`); - return; - } - const histogram = - overallTimeSeries.data.map((o, i) => { - const current = cpgTimeSeries.data.find( - (d1) => d1.key_as_string === o.key_as_string - ) ?? { - doc_count: 0, - }; - return { - key: o.key, - key_as_string: o.key_as_string ?? '', - doc_count_change_point: current.doc_count, - doc_count_overall: Math.max(0, o.doc_count - current.doc_count), - }; - }) ?? []; + const changePointGroupsChunks = chunk(changePointGroups, CHUNK_SIZE); - push( - addChangePointsGroupHistogramAction([ - { - id: cpg.id, - histogram, - }, - ]) - ); + for (const changePointGroupsChunk of changePointGroupsChunks) { + if (shouldStop) { + logDebugMessage('shouldStop abort fetching group histograms.'); + end(); + return; } - }); + + await asyncForEach(changePointGroupsChunk, async (cpg) => { + if (overallTimeSeries !== undefined) { + const histogramQuery = { + bool: { + filter: cpg.group.map((d) => ({ + term: { [d.fieldName]: d.fieldValue }, + })), + }, + }; + + let cpgTimeSeries: NumericChartData; + try { + cpgTimeSeries = ( + (await fetchHistogramsForFields( + client, + request.body.index, + histogramQuery, + // fields + [ + { + fieldName: request.body.timeFieldName, + type: KBN_FIELD_TYPES.DATE, + interval: overallTimeSeries.interval, + min: overallTimeSeries.stats[0], + max: overallTimeSeries.stats[1], + }, + ], + // samplerShardSize + -1, + undefined, + abortSignal + )) as [NumericChartData] + )[0]; + } catch (e) { + if (!isRequestAbortedError(e)) { + logger.error( + `Failed to fetch the histogram data for group #${ + cpg.id + }, got: \n${e.toString()}` + ); + pushError(`Failed to fetch the histogram data for group #${cpg.id}.`); + } + return; + } + const histogram = + overallTimeSeries.data.map((o, i) => { + const current = cpgTimeSeries.data.find( + (d1) => d1.key_as_string === o.key_as_string + ) ?? { + doc_count: 0, + }; + return { + key: o.key, + key_as_string: o.key_as_string ?? '', + doc_count_change_point: current.doc_count, + doc_count_overall: Math.max(0, o.doc_count - current.doc_count), + }; + }) ?? []; + + push( + addChangePointsGroupHistogramAction([ + { + id: cpg.id, + histogram, + }, + ]) + ); + } + }); + } } } catch (e) { - logger.error( - `Failed to transform field/value pairs into groups, got: \n${e.toString()}` - ); - pushError(`Failed to transform field/value pairs into groups.`); + if (!isRequestAbortedError(e)) { + logger.error( + `Failed to transform field/value pairs into groups, got: \n${e.toString()}` + ); + pushError(`Failed to transform field/value pairs into groups.`); + } } } loaded += PROGRESS_STEP_HISTOGRAMS_GROUPS; - logDebugMessage('Fetch field/value histograms.'); + logDebugMessage(`Fetch ${changePoints.length} field/value histograms.`); // time series filtered by fields - if (changePoints && overallTimeSeries !== undefined) { - await asyncForEach(changePoints, async (cp) => { - if (overallTimeSeries !== undefined) { - const histogramQuery = { - bool: { - filter: [ - { - term: { [cp.fieldName]: cp.fieldValue }, - }, - ], - }, - }; - - let cpTimeSeries: NumericChartData; - - try { - cpTimeSeries = ( - (await fetchHistogramsForFields( - client, - request.body.index, - histogramQuery, - // fields - [ + if (changePoints.length > 0 && overallTimeSeries !== undefined) { + const changePointsChunks = chunk(changePoints, CHUNK_SIZE); + + for (const changePointsChunk of changePointsChunks) { + if (shouldStop) { + logDebugMessage('shouldStop abort fetching field/value histograms.'); + end(); + return; + } + + await asyncForEach(changePointsChunk, async (cp) => { + if (overallTimeSeries !== undefined) { + const histogramQuery = { + bool: { + filter: [ { - fieldName: request.body.timeFieldName, - type: KBN_FIELD_TYPES.DATE, - interval: overallTimeSeries.interval, - min: overallTimeSeries.stats[0], - max: overallTimeSeries.stats[1], + term: { [cp.fieldName]: cp.fieldValue }, }, ], - // samplerShardSize - -1, - undefined - )) as [NumericChartData] - )[0]; - } catch (e) { - logger.error( - `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${ - cp.fieldValue - }", got: \n${e.toString()}` - ); - pushError( - `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${cp.fieldValue}".` + }, + }; + + let cpTimeSeries: NumericChartData; + + try { + cpTimeSeries = ( + (await fetchHistogramsForFields( + client, + request.body.index, + histogramQuery, + // fields + [ + { + fieldName: request.body.timeFieldName, + type: KBN_FIELD_TYPES.DATE, + interval: overallTimeSeries.interval, + min: overallTimeSeries.stats[0], + max: overallTimeSeries.stats[1], + }, + ], + // samplerShardSize + -1, + undefined, + abortSignal + )) as [NumericChartData] + )[0]; + } catch (e) { + logger.error( + `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${ + cp.fieldValue + }", got: \n${e.toString()}` + ); + pushError( + `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${cp.fieldValue}".` + ); + return; + } + + const histogram = + overallTimeSeries.data.map((o, i) => { + const current = cpTimeSeries.data.find( + (d1) => d1.key_as_string === o.key_as_string + ) ?? { + doc_count: 0, + }; + return { + key: o.key, + key_as_string: o.key_as_string ?? '', + doc_count_change_point: current.doc_count, + doc_count_overall: Math.max(0, o.doc_count - current.doc_count), + }; + }) ?? []; + + const { fieldName, fieldValue } = cp; + + loaded += (1 / changePoints.length) * PROGRESS_STEP_HISTOGRAMS; + pushHistogramDataLoadingState(); + push( + addChangePointsHistogramAction([ + { + fieldName, + fieldValue, + histogram, + }, + ]) ); - return; } - - const histogram = - overallTimeSeries.data.map((o, i) => { - const current = cpTimeSeries.data.find( - (d1) => d1.key_as_string === o.key_as_string - ) ?? { - doc_count: 0, - }; - return { - key: o.key, - key_as_string: o.key_as_string ?? '', - doc_count_change_point: current.doc_count, - doc_count_overall: Math.max(0, o.doc_count - current.doc_count), - }; - }) ?? []; - - const { fieldName, fieldValue } = cp; - - loaded += (1 / changePoints.length) * PROGRESS_STEP_HISTOGRAMS; - pushHistogramDataLoadingState(); - push( - addChangePointsHistogramAction([ - { - fieldName, - fieldValue, - histogram, - }, - ]) - ); - } - }); + }); + } } endWithUpdatedLoadingState(); } catch (e) { - logger.error(`Explain log rate spikes analysis failed to finish, got: \n${e.toString()}`); - pushError(`Explain log rate spikes analysis failed to finish.`); + if (!isRequestAbortedError(e)) { + logger.error( + `Explain log rate spikes analysis failed to finish, got: \n${e.toString()}` + ); + pushError(`Explain log rate spikes analysis failed to finish.`); + } end(); } } diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts index 0fb7f90c89c12..08165db084670 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts @@ -13,6 +13,8 @@ import { ChangePoint } from '@kbn/ml-agg-utils'; import { SPIKE_ANALYSIS_THRESHOLD } from '../../../common/constants'; import type { AiopsExplainLogRateSpikesSchema } from '../../../common/api/explain_log_rate_spikes'; +import { isRequestAbortedError } from '../../lib/is_request_aborted_error'; + import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; @@ -95,23 +97,49 @@ export const fetchChangePointPValues = async ( params: AiopsExplainLogRateSpikesSchema, fieldNames: string[], logger: Logger, - emitError: (m: string) => void + emitError: (m: string) => void, + abortSignal?: AbortSignal ): Promise => { const result: ChangePoint[] = []; - for (const fieldName of fieldNames) { - const request = getChangePointRequest(params, fieldName); - const resp = await esClient.search(request); - - if (resp.aggregations === undefined) { + const settledPromises = await Promise.allSettled( + fieldNames.map((fieldName) => + esClient.search( + getChangePointRequest(params, fieldName), + { + signal: abortSignal, + maxRetries: 0, + } + ) + ) + ); + + function reportError(fieldName: string, error: unknown) { + if (!isRequestAbortedError(error)) { logger.error( `Failed to fetch p-value aggregation for fieldName "${fieldName}", got: \n${JSON.stringify( - resp, + error, null, 2 )}` ); emitError(`Failed to fetch p-value aggregation for fieldName "${fieldName}".`); + } + } + + for (const [index, settledPromise] of settledPromises.entries()) { + const fieldName = fieldNames[index]; + + if (settledPromise.status === 'rejected') { + reportError(fieldName, settledPromise.reason); + // Still continue the analysis even if individual p-value queries fail. + continue; + } + + const resp = settledPromise.value; + + if (resp.aggregations === undefined) { + reportError(fieldName, resp); // Still continue the analysis even if individual p-value queries fail. continue; } diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_field_candidates.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_field_candidates.ts index 7a761d91c0da5..036d8c0f51fcf 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_field_candidates.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_field_candidates.ts @@ -47,14 +47,18 @@ export const getRandomDocsRequest = ( export const fetchFieldCandidates = async ( esClient: ElasticsearchClient, - params: AiopsExplainLogRateSpikesSchema + params: AiopsExplainLogRateSpikesSchema, + abortSignal?: AbortSignal ): Promise => { const { index } = params; // Get all supported fields - const respMapping = await esClient.fieldCaps({ - index, - fields: '*', - }); + const respMapping = await esClient.fieldCaps( + { + index, + fields: '*', + }, + { signal: abortSignal, maxRetries: 0 } + ); const finalFieldCandidates: Set = new Set([]); const acceptableFields: Set = new Set(); @@ -69,7 +73,10 @@ export const fetchFieldCandidates = async ( } }); - const resp = await esClient.search(getRandomDocsRequest(params)); + const resp = await esClient.search(getRandomDocsRequest(params), { + signal: abortSignal, + maxRetries: 0, + }); const sampledDocs = resp.hits.hits.map((d) => d.fields ?? {}); // Get all field names for each returned doc and flatten it diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts index c9444aaca22af..362cae07273e5 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts @@ -13,6 +13,8 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { Logger } from '@kbn/logging'; import type { ChangePoint, FieldValuePair } from '@kbn/ml-agg-utils'; +const FREQUENT_ITEMS_FIELDS_LIMIT = 15; + interface FrequentItemsAggregation extends estypes.AggregationsSamplerAggregation { fi: { buckets: Array<{ key: Record; doc_count: number; support: number }>; @@ -56,12 +58,22 @@ export async function fetchFrequentItems( deviationMin: number, deviationMax: number, logger: Logger, - emitError: (m: string) => void + emitError: (m: string) => void, + abortSignal?: AbortSignal ) { - // get unique fields from change points - const fields = [...new Set(changePoints.map((t) => t.fieldName))]; + // Sort change points by ascending p-value, necessary to apply the field limit correctly. + const sortedChangePoints = changePoints.slice().sort((a, b) => { + return (a.pValue ?? 0) - (b.pValue ?? 0); + }); + + // Get up to 15 unique fields from change points with retained order + const fields = sortedChangePoints.reduce((p, c) => { + if (p.length < FREQUENT_ITEMS_FIELDS_LIMIT && !p.some((d) => d === c.fieldName)) { + p.push(c.fieldName); + } + return p; + }, []); - // TODO add query params const query = { bool: { minimum_should_match: 2, @@ -76,7 +88,7 @@ export async function fetchFrequentItems( }, }, ], - should: changePoints.map((t) => { + should: sortedChangePoints.map((t) => { return { term: { [t.fieldName]: t.fieldValue } }; }), }, @@ -116,18 +128,20 @@ export async function fetchFrequentItems( }, }; + const esBody = { + query, + aggs, + size: 0, + track_total_hits: true, + }; + const body = await client.search( { index, size: 0, - body: { - query, - aggs, - size: 0, - track_total_hits: true, - }, + body: esBody, }, - { maxRetries: 0 } + { signal: abortSignal, maxRetries: 0 } ); if (body.aggregations === undefined) { @@ -166,7 +180,7 @@ export async function fetchFrequentItems( Object.entries(fis.key).forEach(([key, value]) => { result.set[key] = value[0]; - const pValue = changePoints.find( + const pValue = sortedChangePoints.find( (t) => t.fieldName === key && t.fieldValue === value[0] )?.pValue; diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts index 7138aabe9d263..2b403684c8d53 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts @@ -51,6 +51,7 @@ export enum WriteOperations { UnmuteAlert = 'unmuteAlert', Snooze = 'snooze', BulkEdit = 'bulkEdit', + BulkDelete = 'bulkDelete', Unsnooze = 'unsnooze', } diff --git a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts index e86cd98f4a490..b36dfed980b02 100644 --- a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts +++ b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.test.ts @@ -25,7 +25,7 @@ const rule = { describe('wrapScopedClusterClient', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts b/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts index 9c10e619e3ebb..55dc0a5d62161 100644 --- a/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts +++ b/x-pack/plugins/alerting/server/lib/wrap_search_source_client.test.ts @@ -35,7 +35,7 @@ const createSearchSourceClientMock = () => { describe('wrapSearchSourceClient', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/alerting/server/routes/bulk_delete_rules.test.ts b/x-pack/plugins/alerting/server/routes/bulk_delete_rules.test.ts new file mode 100644 index 0000000000000..8fc27cfbc0054 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/bulk_delete_rules.test.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 { httpServiceMock } from '@kbn/core/server/mocks'; + +import { bulkDeleteRulesRoute } from './bulk_delete_rules'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { mockHandlerArguments } from './_mock_handler_arguments'; +import { rulesClientMock } from '../rules_client.mock'; +import { RuleTypeDisabledError } from '../lib/errors/rule_type_disabled'; +import { verifyApiAccess } from '../lib/license_api_access'; + +const rulesClient = rulesClientMock.create(); + +jest.mock('../lib/license_api_access', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('bulkDeleteRulesRoute', () => { + const bulkDeleteRequest = { filter: '' }; + const bulkDeleteResult = { errors: [], total: 1, taskIdsFailedToBeDeleted: [] }; + + it('should delete rules with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + bulkDeleteRulesRoute({ router, licenseState }); + + const [config, handler] = router.patch.mock.calls[0]; + + expect(config.path).toBe('/internal/alerting/rules/_bulk_delete'); + + rulesClient.bulkDeleteRules.mockResolvedValueOnce(bulkDeleteResult); + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: bulkDeleteRequest, + }, + ['ok'] + ); + + expect(await handler(context, req, res)).toEqual({ + body: bulkDeleteResult, + }); + + expect(rulesClient.bulkDeleteRules).toHaveBeenCalledTimes(1); + expect(rulesClient.bulkDeleteRules.mock.calls[0]).toEqual([bulkDeleteRequest]); + + expect(res.ok).toHaveBeenCalled(); + }); + + it('ensures the license allows bulk deleting rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + rulesClient.bulkDeleteRules.mockResolvedValueOnce(bulkDeleteResult); + + bulkDeleteRulesRoute({ router, licenseState }); + + const [, handler] = router.patch.mock.calls[0]; + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: bulkDeleteRequest, + } + ); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents bulk deleting rules', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('Failure'); + }); + + bulkDeleteRulesRoute({ router, licenseState }); + + const [, handler] = router.patch.mock.calls[0]; + + const [context, req, res] = mockHandlerArguments( + { rulesClient }, + { + body: bulkDeleteRequest, + } + ); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`); + }); + + it('ensures the rule type gets validated for the license', async () => { + const licenseState = licenseStateMock.create(); + const router = httpServiceMock.createRouter(); + + bulkDeleteRulesRoute({ router, licenseState }); + + const [, handler] = router.patch.mock.calls[0]; + + rulesClient.bulkDeleteRules.mockRejectedValue( + new RuleTypeDisabledError('Fail', 'license_invalid') + ); + + const [context, req, res] = mockHandlerArguments({ rulesClient }, { params: {}, body: {} }, [ + 'ok', + 'forbidden', + ]); + + await handler(context, req, res); + + expect(res.forbidden).toHaveBeenCalledWith({ body: { message: 'Fail' } }); + }); +}); diff --git a/x-pack/plugins/alerting/server/routes/bulk_delete_rules.ts b/x-pack/plugins/alerting/server/routes/bulk_delete_rules.ts new file mode 100644 index 0000000000000..1eca663de21a8 --- /dev/null +++ b/x-pack/plugins/alerting/server/routes/bulk_delete_rules.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from '@kbn/core/server'; +import { verifyAccessAndContext, handleDisabledApiKeysError } from './lib'; +import { ILicenseState, RuleTypeDisabledError } from '../lib'; +import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; + +export const bulkDeleteRulesRoute = ({ + router, + licenseState, +}: { + router: IRouter; + licenseState: ILicenseState; +}) => { + router.patch( + { + path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_bulk_delete`, + validate: { + body: schema.object({ + filter: schema.maybe(schema.string()), + ids: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1, maxSize: 1000 })), + }), + }, + }, + handleDisabledApiKeysError( + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async (context, req, res) => { + const rulesClient = (await context.alerting).getRulesClient(); + const { filter, ids } = req.body; + + try { + const result = await rulesClient.bulkDeleteRules({ filter, ids }); + return res.ok({ body: result }); + } catch (e) { + if (e instanceof RuleTypeDisabledError) { + return e.sendResponse(res); + } + throw e; + } + }) + ) + ) + ); +}; diff --git a/x-pack/plugins/alerting/server/routes/index.ts b/x-pack/plugins/alerting/server/routes/index.ts index de9e2f112d9e9..dfb9c290af3d2 100644 --- a/x-pack/plugins/alerting/server/routes/index.ts +++ b/x-pack/plugins/alerting/server/routes/index.ts @@ -38,6 +38,7 @@ import { bulkEditInternalRulesRoute } from './bulk_edit_rules'; import { snoozeRuleRoute } from './snooze_rule'; import { unsnoozeRuleRoute } from './unsnooze_rule'; import { runSoonRoute } from './run_soon'; +import { bulkDeleteRulesRoute } from './bulk_delete_rules'; export interface RouteOptions { router: IRouter; @@ -76,6 +77,7 @@ export function defineRoutes(opts: RouteOptions) { unmuteAlertRoute(router, licenseState); updateRuleApiKeyRoute(router, licenseState); bulkEditInternalRulesRoute(router, licenseState); + bulkDeleteRulesRoute({ router, licenseState }); snoozeRuleRoute(router, licenseState); unsnoozeRuleRoute(router, licenseState); runSoonRoute(router, licenseState); diff --git a/x-pack/plugins/alerting/server/rules_client.mock.ts b/x-pack/plugins/alerting/server/rules_client.mock.ts index 2092b98e48c0d..aa29e64d2f460 100644 --- a/x-pack/plugins/alerting/server/rules_client.mock.ts +++ b/x-pack/plugins/alerting/server/rules_client.mock.ts @@ -36,6 +36,7 @@ const createRulesClientMock = () => { getActionErrorLog: jest.fn(), getSpaceId: jest.fn(), bulkEdit: jest.fn(), + bulkDeleteRules: jest.fn(), snooze: jest.fn(), unsnooze: jest.fn(), calculateIsSnoozedUntil: jest.fn(), diff --git a/x-pack/plugins/alerting/server/rules_client/lib/index.ts b/x-pack/plugins/alerting/server/rules_client/lib/index.ts index f3f11589d2966..a221327d938ef 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/index.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/index.ts @@ -8,5 +8,6 @@ export { mapSortField } from './map_sort_field'; export { validateOperationOnAttributes } from './validate_attributes'; export { retryIfBulkEditConflicts } from './retry_if_bulk_edit_conflicts'; +export { retryIfBulkDeleteConflicts } from './retry_if_bulk_delete_conflicts'; export { applyBulkEditOperation } from './apply_bulk_edit_operation'; export { buildKueryNodeFilter } from './build_kuery_node_filter'; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.test.ts new file mode 100644 index 0000000000000..32a18ea7f0984 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.test.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KueryNode } from '@kbn/es-query'; +import { loggingSystemMock } from '@kbn/core/server/mocks'; + +import { retryIfBulkDeleteConflicts } from './retry_if_bulk_delete_conflicts'; +import { RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; + +const mockFilter: KueryNode = { + type: 'function', + value: 'mock', +}; + +const mockLogger = loggingSystemMock.create().get(); + +const mockSuccessfulResult = { + apiKeysToInvalidate: ['apiKey1'], + errors: [], + taskIdsToDelete: ['taskId1'], +}; + +const error409 = { + message: 'some fake message', + status: 409, + rule: { + id: 'fake_rule_id', + name: 'fake rule name', + }, +}; + +const getOperationConflictsTimes = (times: number) => { + return async () => { + conflictOperationMock(); + times--; + if (times >= 0) { + return { + apiKeysToInvalidate: [], + taskIdsToDelete: [], + errors: [error409], + }; + } + return mockSuccessfulResult; + }; +}; + +const OperationSuccessful = async () => mockSuccessfulResult; +const conflictOperationMock = jest.fn(); + +describe('retryIfBulkDeleteConflicts', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('should work when operation is successful', async () => { + const result = await retryIfBulkDeleteConflicts(mockLogger, OperationSuccessful, mockFilter); + + expect(result).toEqual(mockSuccessfulResult); + }); + + test('should throw error when operation fails', async () => { + await expect( + retryIfBulkDeleteConflicts( + mockLogger, + async () => { + throw Error('Test failure'); + }, + mockFilter + ) + ).rejects.toThrowError('Test failure'); + }); + + test(`should return conflict errors when number of retries exceeds ${RETRY_IF_CONFLICTS_ATTEMPTS}`, async () => { + const result = await retryIfBulkDeleteConflicts( + mockLogger, + getOperationConflictsTimes(RETRY_IF_CONFLICTS_ATTEMPTS + 1), + mockFilter + ); + + expect(result.errors).toEqual([error409]); + expect(mockLogger.warn).toBeCalledWith('Bulk delete rules conflicts, exceeded retries'); + }); + + for (let i = 1; i <= RETRY_IF_CONFLICTS_ATTEMPTS; i++) { + test(`should work when operation conflicts ${i} times`, async () => { + const result = await retryIfBulkDeleteConflicts( + mockLogger, + getOperationConflictsTimes(i), + mockFilter + ); + + expect(conflictOperationMock.mock.calls.length).toBe(i + 1); + expect(result).toStrictEqual(mockSuccessfulResult); + }); + } +}); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.ts b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.ts new file mode 100644 index 0000000000000..529055b85e44a --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_delete_conflicts.ts @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import pMap from 'p-map'; +import { chunk } from 'lodash'; +import { KueryNode } from '@kbn/es-query'; +import { Logger } from '@kbn/core/server'; +import { convertRuleIdsToKueryNode } from '../../lib'; +import { BulkDeleteError } from '../rules_client'; +import { waitBeforeNextRetry, RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; + +const MAX_RULES_IDS_IN_RETRY = 1000; + +export type BulkDeleteOperation = (filter: KueryNode | null) => Promise<{ + apiKeysToInvalidate: string[]; + errors: BulkDeleteError[]; + taskIdsToDelete: string[]; +}>; + +interface ReturnRetry { + apiKeysToInvalidate: string[]; + errors: BulkDeleteError[]; + taskIdsToDelete: string[]; +} + +/** + * Retries BulkDelete requests + * If in response are presents conflicted savedObjects(409 statusCode), this util constructs filter with failed SO ids and retries bulkDelete operation until + * all SO updated or number of retries exceeded + * @param logger + * @param bulkEditOperation + * @param filter - KueryNode filter + * @param retries - number of retries left + * @param accApiKeysToInvalidate - accumulated apiKeys that need to be invalidated + * @param accErrors - accumulated conflict errors + * @param accTaskIdsToDelete - accumulated task ids + * @returns Promise + */ +export const retryIfBulkDeleteConflicts = async ( + logger: Logger, + bulkDeleteOperation: BulkDeleteOperation, + filter: KueryNode | null, + retries: number = RETRY_IF_CONFLICTS_ATTEMPTS, + accApiKeysToInvalidate: string[] = [], + accErrors: BulkDeleteError[] = [], + accTaskIdsToDelete: string[] = [] +): Promise => { + try { + const { + apiKeysToInvalidate: currentApiKeysToInvalidate, + errors: currentErrors, + taskIdsToDelete: currentTaskIdsToDelete, + } = await bulkDeleteOperation(filter); + + const apiKeysToInvalidate = [...accApiKeysToInvalidate, ...currentApiKeysToInvalidate]; + const taskIdsToDelete = [...accTaskIdsToDelete, ...currentTaskIdsToDelete]; + const errors = + retries <= 0 + ? [...accErrors, ...currentErrors] + : [...accErrors, ...currentErrors.filter((error) => error.status !== 409)]; + + const ruleIdsWithConflictError = currentErrors.reduce((acc, error) => { + if (error.status === 409) { + return [...acc, error.rule.id]; + } + return acc; + }, []); + + if (ruleIdsWithConflictError.length === 0) { + return { + apiKeysToInvalidate, + errors, + taskIdsToDelete, + }; + } + + if (retries <= 0) { + logger.warn('Bulk delete rules conflicts, exceeded retries'); + + return { + apiKeysToInvalidate, + errors, + taskIdsToDelete, + }; + } + + logger.debug( + `Bulk delete rules conflicts, retrying ..., ${ruleIdsWithConflictError.length} saved objects conflicted` + ); + + await waitBeforeNextRetry(retries); + + // here, we construct filter query with ids. But, due to a fact that number of conflicted saved objects can exceed few thousands we can encounter following error: + // "all shards failed: search_phase_execution_exception: [query_shard_exception] Reason: failed to create query: maxClauseCount is set to 2621" + // That's why we chunk processing ids into pieces by size equals to MAX_RULES_IDS_IN_RETRY + return ( + await pMap( + chunk(ruleIdsWithConflictError, MAX_RULES_IDS_IN_RETRY), + async (queryIds) => + retryIfBulkDeleteConflicts( + logger, + bulkDeleteOperation, + convertRuleIdsToKueryNode(queryIds), + retries - 1, + apiKeysToInvalidate, + errors, + taskIdsToDelete + ), + { + concurrency: 1, + } + ) + ).reduce( + (acc, item) => { + return { + apiKeysToInvalidate: [...acc.apiKeysToInvalidate, ...item.apiKeysToInvalidate], + errors: [...acc.errors, ...item.errors], + taskIdsToDelete: [...acc.taskIdsToDelete, ...item.taskIdsToDelete], + }; + }, + { apiKeysToInvalidate: [], errors: [], taskIdsToDelete: [] } + ); + } catch (err) { + throw err; + } +}; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.test.ts index ae2a83614ac20..5053b367b938e 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.test.ts @@ -6,10 +6,8 @@ */ import { KueryNode } from '@kbn/es-query'; -import { - retryIfBulkEditConflicts, - RetryForConflictsAttempts, -} from './retry_if_bulk_edit_conflicts'; +import { retryIfBulkEditConflicts } from './retry_if_bulk_edit_conflicts'; +import { RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; import { loggingSystemMock } from '@kbn/core/server/mocks'; const mockFilter: KueryNode = { @@ -112,11 +110,11 @@ describe('retryIfBulkEditConflicts', () => { ).rejects.toThrowError('Test failure'); }); - test(`should return conflict errors when number of retries exceeds ${RetryForConflictsAttempts}`, async () => { + test(`should return conflict errors when number of retries exceeds ${RETRY_IF_CONFLICTS_ATTEMPTS}`, async () => { const result = await retryIfBulkEditConflicts( mockLogger, mockOperationName, - getOperationConflictsTimes(RetryForConflictsAttempts + 1), + getOperationConflictsTimes(RETRY_IF_CONFLICTS_ATTEMPTS + 1), mockFilter ); @@ -132,7 +130,7 @@ describe('retryIfBulkEditConflicts', () => { expect(mockLogger.warn).toBeCalledWith(`${mockOperationName} conflicts, exceeded retries`); }); - for (let i = 1; i <= RetryForConflictsAttempts; i++) { + for (let i = 1; i <= RETRY_IF_CONFLICTS_ATTEMPTS; i++) { test(`should work when operation conflicts ${i} times`, async () => { const result = await retryIfBulkEditConflicts( mockLogger, diff --git a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.ts b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.ts index 9e1e60acb768f..550e13a6bffe5 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/retry_if_bulk_edit_conflicts.ts @@ -12,15 +12,7 @@ import { Logger, SavedObjectsBulkUpdateObject, SavedObjectsUpdateResponse } from import { convertRuleIdsToKueryNode } from '../../lib'; import { BulkEditError } from '../rules_client'; import { RawRule } from '../../types'; - -// number of times to retry when conflicts occur -export const RetryForConflictsAttempts = 2; - -// milliseconds to wait before retrying when conflicts occur -// note: we considered making this random, to help avoid a stampede, but -// with 1 retry it probably doesn't matter, and adding randomness could -// make it harder to diagnose issues -const RetryForConflictsDelay = 250; +import { waitBeforeNextRetry, RETRY_IF_CONFLICTS_ATTEMPTS } from './wait_before_next_retry'; // max number of failed SO ids in one retry filter const MaxIdsNumberInRetryFilter = 1000; @@ -57,7 +49,7 @@ export const retryIfBulkEditConflicts = async ( name: string, bulkEditOperation: BulkEditOperation, filter: KueryNode | null, - retries: number = RetryForConflictsAttempts, + retries: number = RETRY_IF_CONFLICTS_ATTEMPTS, accApiKeysToInvalidate: string[] = [], accResults: Array> = [], accErrors: BulkEditError[] = [] @@ -154,13 +146,3 @@ export const retryIfBulkEditConflicts = async ( throw err; } }; - -// exponential delay before retry with adding random delay -async function waitBeforeNextRetry(retries: number): Promise { - const exponentialDelayMultiplier = 1 + (RetryForConflictsAttempts - retries) ** 2; - const randomDelayMs = Math.floor(Math.random() * 100); - - await new Promise((resolve) => - setTimeout(resolve, RetryForConflictsDelay * exponentialDelayMultiplier + randomDelayMs) - ); -} diff --git a/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.test.ts new file mode 100644 index 0000000000000..2fd74a7e08a7c --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.test.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 { + getExponentialDelayMultiplier, + randomDelayMs, + RETRY_IF_CONFLICTS_DELAY, + RETRY_IF_CONFLICTS_ATTEMPTS, + waitBeforeNextRetry, +} from './wait_before_next_retry'; + +describe('waitBeforeNextRetry', () => { + const randomDelayPart = 0.1; + + beforeEach(() => { + jest.spyOn(global.Math, 'random').mockReturnValue(randomDelayPart); + jest.spyOn(window, 'setTimeout'); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + + for (let i = 1; i <= RETRY_IF_CONFLICTS_ATTEMPTS; i++) { + it(`should set timout for ${i} tries`, async () => { + await waitBeforeNextRetry(i); + expect(setTimeout).toBeCalledTimes(1); + expect(setTimeout).toHaveBeenCalledWith( + expect.any(Function), + RETRY_IF_CONFLICTS_DELAY * getExponentialDelayMultiplier(i) + randomDelayMs + ); + }); + } +}); diff --git a/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.ts b/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.ts new file mode 100644 index 0000000000000..f836de26c4188 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/lib/wait_before_next_retry.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. + */ + +export const RETRY_IF_CONFLICTS_ATTEMPTS = 2; + +// milliseconds to wait before retrying when conflicts occur +// note: we considered making this random, to help avoid a stampede, but +// with 1 retry it probably doesn't matter, and adding randomness could +// make it harder to diagnose issues +export const RETRY_IF_CONFLICTS_DELAY = 250; + +export const randomDelayMs = Math.floor(Math.random() * 100); +export const getExponentialDelayMultiplier = (retries: number) => + 1 + (RETRY_IF_CONFLICTS_ATTEMPTS - retries) ** 2; + +/** + * exponential delay before retry with adding random delay + */ +export const waitBeforeNextRetry = async (retries: number): Promise => { + const exponentialDelayMultiplier = getExponentialDelayMultiplier(retries); + + await new Promise((resolve) => + setTimeout(resolve, RETRY_IF_CONFLICTS_DELAY * exponentialDelayMultiplier + randomDelayMs) + ); +}; diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index d08f12f054a50..777cf340b53e7 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -33,6 +33,7 @@ import { SavedObjectsUtils, SavedObjectAttributes, SavedObjectsBulkUpdateObject, + SavedObjectsBulkDeleteObject, SavedObjectsUpdateResponse, } from '@kbn/core/server'; import { ActionsClient, ActionsAuthorization } from '@kbn/actions-plugin/server'; @@ -104,6 +105,7 @@ import { mapSortField, validateOperationOnAttributes, retryIfBulkEditConflicts, + retryIfBulkDeleteConflicts, applyBulkEditOperation, buildKueryNodeFilter, } from './lib'; @@ -178,7 +180,7 @@ export interface RuleAggregation { }; } -export interface RuleBulkEditAggregation { +export interface RuleBulkOperationAggregation { alertTypeId: { buckets: Array<{ key: string[]; @@ -297,6 +299,16 @@ export type BulkEditOptions = | BulkEditOptionsFilter | BulkEditOptionsIds; +export interface BulkDeleteOptionsFilter { + filter?: string | KueryNode; +} + +export interface BulkDeleteOptionsIds { + ids?: string[]; +} + +export type BulkDeleteOptions = BulkDeleteOptionsFilter | BulkDeleteOptionsIds; + export interface BulkEditError { message: string; rule: { @@ -305,6 +317,15 @@ export interface BulkEditError { }; } +export interface BulkDeleteError { + message: string; + status: number; + rule: { + id: string; + name: string; + }; +} + export interface AggregateOptions extends IndexType { search?: string; defaultSearchOperator?: 'AND' | 'OR'; @@ -433,7 +454,7 @@ const extractedSavedObjectParamReferenceNamePrefix = 'param:'; // NOTE: Changing this prefix will require a migration to update the prefix in all existing `rule` saved objects const preconfiguredConnectorActionRefPrefix = 'preconfigured:'; -const MAX_RULES_NUMBER_FOR_BULK_EDIT = 10000; +const MAX_RULES_NUMBER_FOR_BULK_OPERATION = 10000; const API_KEY_GENERATE_CONCURRENCY = 50; const RULE_TYPE_CHECKS_CONCURRENCY = 50; @@ -1695,6 +1716,212 @@ export class RulesClient { ); } + private getAuthorizationFilter = async () => { + try { + const authorizationTuple = await this.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + alertingAuthorizationFilterOpts + ); + return authorizationTuple.filter; + } catch (error) { + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DELETE, + error, + }) + ); + throw error; + } + }; + + public bulkDeleteRules = async (options: BulkDeleteOptions) => { + const filter = (options as BulkDeleteOptionsFilter).filter; + const ids = (options as BulkDeleteOptionsIds).ids; + + if (!ids && !filter) { + throw Boom.badRequest( + "Either 'ids' or 'filter' property in method's arguments should be provided" + ); + } + + if (ids?.length === 0) { + throw Boom.badRequest("'ids' property should not be an empty array"); + } + + if (ids && filter) { + throw Boom.badRequest( + "Both 'filter' and 'ids' are supplied. Define either 'ids' or 'filter' properties in method's arguments" + ); + } + + const kueryNodeFilter = ids ? convertRuleIdsToKueryNode(ids) : buildKueryNodeFilter(filter); + const authorizationFilter = await this.getAuthorizationFilter(); + + const kueryNodeFilterWithAuth = + authorizationFilter && kueryNodeFilter + ? nodeBuilder.and([kueryNodeFilter, authorizationFilter as KueryNode]) + : kueryNodeFilter; + + const { aggregations, total } = await this.unsecuredSavedObjectsClient.find< + RawRule, + RuleBulkOperationAggregation + >({ + filter: kueryNodeFilterWithAuth, + page: 1, + perPage: 0, + type: 'alert', + aggs: { + alertTypeId: { + multi_terms: { + terms: [ + { field: 'alert.attributes.alertTypeId' }, + { field: 'alert.attributes.consumer' }, + ], + }, + }, + }, + }); + + if (total > MAX_RULES_NUMBER_FOR_BULK_OPERATION) { + throw Boom.badRequest( + `More than ${MAX_RULES_NUMBER_FOR_BULK_OPERATION} rules matched for bulk delete` + ); + } + + const buckets = aggregations?.alertTypeId.buckets; + + if (buckets === undefined || buckets?.length === 0) { + throw Boom.badRequest('No rules found for bulk delete'); + } + + await pMap( + buckets, + async ({ key: [ruleType, consumer] }) => { + this.ruleTypeRegistry.ensureRuleTypeEnabled(ruleType); + try { + await this.authorization.ensureAuthorized({ + ruleTypeId: ruleType, + consumer, + operation: WriteOperations.BulkDelete, + entity: AlertingAuthorizationEntity.Rule, + }); + } catch (error) { + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DELETE, + error, + }) + ); + throw error; + } + }, + { concurrency: RULE_TYPE_CHECKS_CONCURRENCY } + ); + + const { apiKeysToInvalidate, errors, taskIdsToDelete } = await retryIfBulkDeleteConflicts( + this.logger, + (filterKueryNode: KueryNode | null) => this.bulkDeleteWithOCC({ filter: filterKueryNode }), + kueryNodeFilterWithAuth + ); + + const taskIdsFailedToBeDeleted: string[] = []; + if (taskIdsToDelete.length > 0) { + try { + const resultFromDeletingTasks = await this.taskManager.bulkRemoveIfExist(taskIdsToDelete); + resultFromDeletingTasks?.statuses.forEach((status) => { + if (!status.success) { + taskIdsFailedToBeDeleted.push(status.id); + } + }); + this.logger.debug( + `Successfully deleted schedules for underlying tasks: ${taskIdsToDelete + .filter((id) => taskIdsFailedToBeDeleted.includes(id)) + .join(', ')}` + ); + } catch (error) { + this.logger.error( + `Failure to delete schedules for underlying tasks: ${taskIdsToDelete.join( + ', ' + )}. TaskManager bulkRemoveIfExist failed with Error: ${error.message}` + ); + } + } + + await bulkMarkApiKeysForInvalidation( + { apiKeys: apiKeysToInvalidate }, + this.logger, + this.unsecuredSavedObjectsClient + ); + + return { errors, total, taskIdsFailedToBeDeleted }; + }; + + private bulkDeleteWithOCC = async ({ filter }: { filter: KueryNode | null }) => { + const rulesFinder = + await this.encryptedSavedObjectsClient.createPointInTimeFinderDecryptedAsInternalUser( + { + filter, + type: 'alert', + perPage: 100, + ...(this.namespace ? { namespaces: [this.namespace] } : undefined), + } + ); + + const rules: SavedObjectsBulkDeleteObject[] = []; + const apiKeysToInvalidate: string[] = []; + const taskIdsToDelete: string[] = []; + const errors: BulkDeleteError[] = []; + const apiKeyToRuleIdMapping: Record = {}; + const taskIdToRuleIdMapping: Record = {}; + const ruleNameToRuleIdMapping: Record = {}; + + for await (const response of rulesFinder.find()) { + for (const rule of response.saved_objects) { + if (rule.attributes.apiKey) { + apiKeyToRuleIdMapping[rule.id] = rule.attributes.apiKey; + } + if (rule.attributes.name) { + ruleNameToRuleIdMapping[rule.id] = rule.attributes.name; + } + if (rule.attributes.scheduledTaskId) { + taskIdToRuleIdMapping[rule.id] = rule.attributes.scheduledTaskId; + } + rules.push(rule); + + this.auditLogger?.log( + ruleAuditEvent({ + action: RuleAuditAction.DELETE, + outcome: 'unknown', + savedObject: { type: 'alert', id: rule.id }, + }) + ); + } + } + + const result = await this.unsecuredSavedObjectsClient.bulkDelete(rules); + + result.statuses.forEach((status) => { + if (status.error === undefined) { + if (apiKeyToRuleIdMapping[status.id]) { + apiKeysToInvalidate.push(apiKeyToRuleIdMapping[status.id]); + } + if (taskIdToRuleIdMapping[status.id]) { + taskIdsToDelete.push(taskIdToRuleIdMapping[status.id]); + } + } else { + errors.push({ + message: status.error.message ?? 'n/a', + status: status.error.statusCode, + rule: { + id: status.id, + name: ruleNameToRuleIdMapping[status.id] ?? 'n/a', + }, + }); + } + }); + return { apiKeysToInvalidate, errors, taskIdsToDelete }; + }; + public async bulkEdit( options: BulkEditOptions ): Promise<{ @@ -1737,7 +1964,7 @@ export class RulesClient { const { aggregations, total } = await this.unsecuredSavedObjectsClient.find< RawRule, - RuleBulkEditAggregation + RuleBulkOperationAggregation >({ filter: qNodeFilterWithAuth, page: 1, @@ -1755,9 +1982,9 @@ export class RulesClient { }, }); - if (total > MAX_RULES_NUMBER_FOR_BULK_EDIT) { + if (total > MAX_RULES_NUMBER_FOR_BULK_OPERATION) { throw Boom.badRequest( - `More than ${MAX_RULES_NUMBER_FOR_BULK_EDIT} rules matched for bulk edit` + `More than ${MAX_RULES_NUMBER_FOR_BULK_OPERATION} rules matched for bulk edit` ); } const buckets = aggregations?.alertTypeId.buckets; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts new file mode 100644 index 0000000000000..5e0a71edcae51 --- /dev/null +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts @@ -0,0 +1,492 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RulesClient, ConstructorOptions } from '../rules_client'; +import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; +import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; +import { RecoveredActionGroup } from '../../../common'; +import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; +import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; +import { ActionsAuthorization } from '@kbn/actions-plugin/server'; +import { auditLoggerMock } from '@kbn/security-plugin/server/audit/mocks'; +import { getBeforeSetup, setGlobalDate } from './lib'; +import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; +import { loggerMock } from '@kbn/logging-mocks'; + +jest.mock('../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation', () => ({ + bulkMarkApiKeysForInvalidation: jest.fn(), +})); + +const taskManager = taskManagerMock.createStart(); +const ruleTypeRegistry = ruleTypeRegistryMock.create(); +const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); +const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); +const authorization = alertingAuthorizationMock.create(); +const actionsAuthorization = actionsAuthorizationMock.create(); +const auditLogger = auditLoggerMock.create(); +const logger = loggerMock.create(); + +const kibanaVersion = 'v8.2.0'; +const createAPIKeyMock = jest.fn(); +const rulesClientParams: jest.Mocked = { + taskManager, + ruleTypeRegistry, + unsecuredSavedObjectsClient, + authorization: authorization as unknown as AlertingAuthorization, + actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, + spaceId: 'default', + namespace: 'default', + getUserName: jest.fn(), + createAPIKey: createAPIKeyMock, + logger, + encryptedSavedObjectsClient: encryptedSavedObjects, + getActionsClient: jest.fn(), + getEventLogClient: jest.fn(), + kibanaVersion, + auditLogger, + minimumScheduleInterval: { value: '1m', enforce: false }, +}; + +beforeEach(() => { + getBeforeSetup(rulesClientParams, taskManager, ruleTypeRegistry); + (auditLogger.log as jest.Mock).mockClear(); +}); + +setGlobalDate(); + +describe('bulkDelete', () => { + let rulesClient: RulesClient; + const existingRule = { + id: 'id1', + type: 'alert', + attributes: {}, + references: [], + version: '123', + }; + const existingDecryptedRule1 = { + ...existingRule, + attributes: { + ...existingRule.attributes, + scheduledTaskId: 'taskId1', + apiKey: Buffer.from('123:abc').toString('base64'), + }, + }; + const existingDecryptedRule2 = { + ...existingRule, + id: 'id2', + attributes: { + ...existingRule.attributes, + scheduledTaskId: 'taskId2', + apiKey: Buffer.from('321:abc').toString('base64'), + }, + }; + + const mockCreatePointInTimeFinderAsInternalUser = ( + response = { saved_objects: [existingDecryptedRule1, existingDecryptedRule2] } + ) => { + encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockResolvedValue({ + close: jest.fn(), + find: function* asyncGenerator() { + yield response; + }, + }); + }; + + beforeEach(async () => { + rulesClient = new RulesClient(rulesClientParams); + authorization.getFindAuthorizationFilter.mockResolvedValue({ + ensureRuleTypeIsAuthorized() {}, + }); + + unsecuredSavedObjectsClient.find.mockResolvedValue({ + aggregations: { + alertTypeId: { + buckets: [{ key: ['myType', 'myApp'], key_as_string: 'myType|myApp', doc_count: 2 }], + }, + }, + saved_objects: [], + per_page: 0, + page: 0, + total: 2, + }); + + ruleTypeRegistry.get.mockReturnValue({ + id: 'myType', + name: 'Test', + actionGroups: [ + { id: 'default', name: 'Default' }, + { id: 'custom', name: 'Not the Default' }, + ], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + recoveryActionGroup: RecoveredActionGroup, + async executor() {}, + producer: 'alerts', + }); + }); + + test('should try to delete rules, one successful and one with 500 error', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { + id: 'id2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 500, + }, + }, + ], + }); + + const result = await rulesClient.bulkDeleteRules({ filter: 'fake_filter' }); + + expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledTimes(1); + expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledWith([ + existingDecryptedRule1, + existingDecryptedRule2, + ]); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledTimes(1); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['taskId1']); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( + { apiKeys: ['MTIzOmFiYw=='] }, + expect.anything(), + expect.anything() + ); + expect(result).toStrictEqual({ + errors: [{ message: 'UPS', rule: { id: 'id2', name: 'n/a' }, status: 500 }], + total: 2, + taskIdsFailedToBeDeleted: [], + }); + }); + + test('should try to delete rules, one successful and one with 409 error, which will not be deleted with retry', async () => { + unsecuredSavedObjectsClient.bulkDelete + .mockResolvedValueOnce({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { + id: 'id2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + }, + ], + }) + .mockResolvedValueOnce({ + statuses: [ + { + id: 'id2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + }, + ], + }) + .mockResolvedValueOnce({ + statuses: [ + { + id: 'id2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + }, + ], + }); + + encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule1, existingDecryptedRule2] }; + }, + }) + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule2] }; + }, + }) + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule2] }; + }, + }); + + const result = await rulesClient.bulkDeleteRules({ ids: ['id1', 'id2'] }); + + expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledTimes(3); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledTimes(1); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['taskId1']); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( + { apiKeys: ['MTIzOmFiYw=='] }, + expect.anything(), + expect.anything() + ); + expect(result).toStrictEqual({ + errors: [{ message: 'UPS', rule: { id: 'id2', name: 'n/a' }, status: 409 }], + total: 2, + taskIdsFailedToBeDeleted: [], + }); + }); + + test('should try to delete rules, one successful and one with 409 error, which successfully will be deleted with retry', async () => { + unsecuredSavedObjectsClient.bulkDelete + .mockResolvedValueOnce({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { + id: 'id2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 409, + }, + }, + ], + }) + .mockResolvedValueOnce({ + statuses: [ + { + id: 'id2', + type: 'alert', + success: true, + }, + ], + }); + + encryptedSavedObjects.createPointInTimeFinderDecryptedAsInternalUser = jest + .fn() + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule1, existingDecryptedRule2] }; + }, + }) + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule2] }; + }, + }) + .mockResolvedValueOnce({ + close: jest.fn(), + find: function* asyncGenerator() { + yield { saved_objects: [existingDecryptedRule2] }; + }, + }); + + const result = await rulesClient.bulkDeleteRules({ ids: ['id1', 'id2'] }); + + expect(unsecuredSavedObjectsClient.bulkDelete).toHaveBeenCalledTimes(2); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledTimes(1); + expect(taskManager.bulkRemoveIfExist).toHaveBeenCalledWith(['taskId1', 'taskId2']); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledTimes(1); + expect(bulkMarkApiKeysForInvalidation).toHaveBeenCalledWith( + { apiKeys: ['MTIzOmFiYw==', 'MzIxOmFiYw=='] }, + expect.anything(), + expect.anything() + ); + expect(result).toStrictEqual({ + errors: [], + total: 2, + taskIdsFailedToBeDeleted: [], + }); + }); + + test('should thow an error if number of matched rules greater than 10,000', async () => { + unsecuredSavedObjectsClient.find.mockResolvedValue({ + aggregations: { + alertTypeId: { + buckets: [{ key: ['myType', 'myApp'], key_as_string: 'myType|myApp', doc_count: 2 }], + }, + }, + saved_objects: [], + per_page: 0, + page: 0, + total: 10001, + }); + + await expect(rulesClient.bulkDeleteRules({ filter: 'fake_filter' })).rejects.toThrow( + 'More than 10000 rules matched for bulk delete' + ); + }); + + test('should throw an error if we do not get buckets', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + unsecuredSavedObjectsClient.find.mockResolvedValue({ + aggregations: { + alertTypeId: {}, + }, + saved_objects: [], + per_page: 0, + page: 0, + total: 2, + }); + + await expect(rulesClient.bulkDeleteRules({ filter: 'fake_filter' })).rejects.toThrow( + 'No rules found for bulk delete' + ); + }); + + describe('taskManager', () => { + test('should return task id if deleting task failed', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { id: 'id2', type: 'alert', success: true }, + ], + }); + taskManager.bulkRemoveIfExist.mockImplementation(async () => ({ + statuses: [ + { + id: 'taskId1', + type: 'alert', + success: true, + }, + { + id: 'taskId2', + type: 'alert', + success: false, + error: { + error: '', + message: 'UPS', + statusCode: 500, + }, + }, + ], + })); + + const result = await rulesClient.bulkDeleteRules({ filter: 'fake_filter' }); + + expect(logger.debug).toBeCalledTimes(1); + expect(logger.debug).toBeCalledWith( + 'Successfully deleted schedules for underlying tasks: taskId2' + ); + expect(result).toStrictEqual({ + errors: [], + total: 2, + taskIdsFailedToBeDeleted: ['taskId2'], + }); + }); + + test('should not throw an error if taskManager throw an error', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { id: 'id2', type: 'alert', success: true }, + ], + }); + taskManager.bulkRemoveIfExist.mockImplementation(() => { + throw new Error('UPS'); + }); + + const result = await rulesClient.bulkDeleteRules({ filter: 'fake_filter' }); + + expect(logger.error).toBeCalledTimes(1); + expect(logger.error).toBeCalledWith( + 'Failure to delete schedules for underlying tasks: taskId1, taskId2. TaskManager bulkRemoveIfExist failed with Error: UPS' + ); + expect(result).toStrictEqual({ + errors: [], + taskIdsFailedToBeDeleted: [], + total: 2, + }); + }); + }); + + describe('auditLogger', () => { + jest.spyOn(auditLogger, 'log').mockImplementation(); + + test('logs audit event when deleting rules', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [ + { id: 'id1', type: 'alert', success: true }, + { id: 'id2', type: 'alert', success: true }, + ], + }); + + await rulesClient.bulkDeleteRules({ filter: 'fake_filter' }); + + expect(auditLogger.log.mock.calls[0][0]?.event?.action).toEqual('rule_delete'); + expect(auditLogger.log.mock.calls[0][0]?.event?.outcome).toEqual('unknown'); + expect(auditLogger.log.mock.calls[0][0]?.kibana).toEqual({ + saved_object: { id: 'id1', type: 'alert' }, + }); + expect(auditLogger.log.mock.calls[1][0]?.event?.action).toEqual('rule_delete'); + expect(auditLogger.log.mock.calls[1][0]?.event?.outcome).toEqual('unknown'); + expect(auditLogger.log.mock.calls[1][0]?.kibana).toEqual({ + saved_object: { id: 'id2', type: 'alert' }, + }); + }); + + test('logs audit event when authentication failed', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + authorization.ensureAuthorized.mockImplementation(() => { + throw new Error('Unauthorized'); + }); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [{ id: 'id1', type: 'alert', success: true }], + }); + + await expect(rulesClient.bulkDeleteRules({ filter: 'fake_filter' })).rejects.toThrowError( + 'Unauthorized' + ); + + expect(auditLogger.log.mock.calls[0][0]?.event?.action).toEqual('rule_delete'); + expect(auditLogger.log.mock.calls[0][0]?.event?.outcome).toEqual('failure'); + }); + + test('logs audit event when getting an authorization filter failed', async () => { + mockCreatePointInTimeFinderAsInternalUser(); + authorization.getFindAuthorizationFilter.mockImplementation(() => { + throw new Error('Error'); + }); + unsecuredSavedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [{ id: 'id1', type: 'alert', success: true }], + }); + + await expect(rulesClient.bulkDeleteRules({ filter: 'fake_filter' })).rejects.toThrowError( + 'Error' + ); + + expect(auditLogger.log.mock.calls[0][0]?.event?.action).toEqual('rule_delete'); + expect(auditLogger.log.mock.calls[0][0]?.event?.outcome).toEqual('failure'); + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/lib.ts b/x-pack/plugins/alerting/server/rules_client/tests/lib.ts index 37eccba1d0d56..6a1e84b38d039 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/lib.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/lib.ts @@ -51,6 +51,9 @@ export function getBeforeSetup( rulesClientParams.createAPIKey.mockResolvedValue({ apiKeysEnabled: false }); rulesClientParams.getUserName.mockResolvedValue('elastic'); taskManager.runSoon.mockResolvedValue({ id: '' }); + taskManager.bulkRemoveIfExist.mockResolvedValue({ + statuses: [{ id: 'taskId', type: 'alert', success: true }], + }); const actionsClient = actionsClientMock.create(); actionsClient.getBulk.mockResolvedValueOnce([ diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index f1917a079a26d..9326f30dd7828 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -90,21 +90,21 @@ export interface RuleExecutorOptions< InstanceContext extends AlertInstanceContext = never, ActionGroupIds extends string = never > { - alertId: string; + alertId: string; // Is actually the Rule ID. Will be updated as part of https://github.com/elastic/kibana/issues/100115 + createdBy: string | null; executionId: string; - startedAt: Date; - previousStartedAt: Date | null; - services: RuleExecutorServices; + logger: Logger; + name: string; params: Params; - state: State; + previousStartedAt: Date | null; rule: SanitizedRuleConfig; + services: RuleExecutorServices; spaceId: string; - namespace?: string; - name: string; + startedAt: Date; + state: State; tags: string[]; - createdBy: string | null; updatedBy: string | null; - logger: Logger; + namespace?: string; } export interface RuleParamsAndRefs { diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index 95c36d24aad5b..8a1ae899ddd3b 100644 --- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -75,6 +75,8 @@ exports[`Error FAAS_DURATION 1`] = `undefined`; exports[`Error FAAS_ID 1`] = `undefined`; +exports[`Error FAAS_NAME 1`] = `undefined`; + exports[`Error FAAS_TRIGGER_TYPE 1`] = `undefined`; exports[`Error HOST 1`] = ` @@ -330,6 +332,8 @@ exports[`Span FAAS_DURATION 1`] = `undefined`; exports[`Span FAAS_ID 1`] = `undefined`; +exports[`Span FAAS_NAME 1`] = `undefined`; + exports[`Span FAAS_TRIGGER_TYPE 1`] = `undefined`; exports[`Span HOST 1`] = `undefined`; @@ -581,6 +585,8 @@ exports[`Transaction FAAS_DURATION 1`] = `undefined`; exports[`Transaction FAAS_ID 1`] = `undefined`; +exports[`Transaction FAAS_NAME 1`] = `undefined`; + exports[`Transaction FAAS_TRIGGER_TYPE 1`] = `undefined`; exports[`Transaction HOST 1`] = ` diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_bulk_schema.ts b/x-pack/plugins/apm/common/agent_configuration/runtime_types/trace_continuation_strategy_rt.ts similarity index 59% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_bulk_schema.ts rename to x-pack/plugins/apm/common/agent_configuration/runtime_types/trace_continuation_strategy_rt.ts index 538348dcc3275..13eed0a78f966 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_bulk_schema.ts +++ b/x-pack/plugins/apm/common/agent_configuration/runtime_types/trace_continuation_strategy_rt.ts @@ -6,7 +6,9 @@ */ import * as t from 'io-ts'; -import { updateRulesSchema } from './rule_schemas'; -export const updateRulesBulkSchema = t.array(updateRulesSchema); -export type UpdateRulesBulkSchema = t.TypeOf; +export const traceContinuationStrategyRt = t.union([ + t.literal('continue'), + t.literal('restart'), + t.literal('restart_external'), +]); diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap index fc42af5ff7724..a4bc508856b45 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap @@ -47,21 +47,57 @@ Array [ "type": "select", "validationName": "(\\"off\\" | \\"errors\\" | \\"transactions\\" | \\"all\\")", }, + Object { + "key": "capture_body_content_types", + "type": "text", + "validationName": "string", + }, Object { "key": "capture_headers", "type": "boolean", "validationName": "(\\"true\\" | \\"false\\")", }, + Object { + "key": "capture_jmx_metrics", + "type": "text", + "validationName": "string", + }, Object { "key": "circuit_breaker_enabled", "type": "boolean", "validationName": "(\\"true\\" | \\"false\\")", }, + Object { + "key": "dedot_custom_metrics", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, Object { "key": "enable_log_correlation", "type": "boolean", "validationName": "(\\"true\\" | \\"false\\")", }, + Object { + "key": "exit_span_min_duration", + "min": "0ms", + "type": "duration", + "units": Array [ + "ms", + "s", + "m", + ], + "validationName": "durationRt", + }, + Object { + "key": "ignore_exceptions", + "type": "text", + "validationName": "string", + }, + Object { + "key": "ignore_message_queues", + "type": "text", + "validationName": "string", + }, Object { "key": "log_level", "options": Array [ @@ -156,6 +192,33 @@ Array [ ], "validationName": "durationRt", }, + Object { + "key": "span_compression_enabled", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, + Object { + "key": "span_compression_exact_match_max_duration", + "min": "0ms", + "type": "duration", + "units": Array [ + "ms", + "s", + "m", + ], + "validationName": "durationRt", + }, + Object { + "key": "span_compression_same_kind_max_duration", + "min": "0ms", + "type": "duration", + "units": Array [ + "ms", + "s", + "m", + ], + "validationName": "durationRt", + }, Object { "key": "span_frames_min_duration", "min": "-1ms", @@ -205,11 +268,40 @@ Array [ "type": "float", "validationName": "floatRt", }, + Object { + "key": "trace_continuation_strategy", + "options": Array [ + Object { + "text": "continue", + "value": "continue", + }, + Object { + "text": "restart", + "value": "restart", + }, + Object { + "text": "restart_external", + "value": "restart_external", + }, + ], + "type": "select", + "validationName": "(\\"continue\\" | \\"restart\\" | \\"restart_external\\")", + }, + Object { + "key": "trace_methods", + "type": "text", + "validationName": "string", + }, Object { "key": "transaction_ignore_urls", "type": "text", "validationName": "string", }, + Object { + "key": "transaction_ignore_user_agents", + "type": "text", + "validationName": "string", + }, Object { "key": "transaction_max_spans", "max": undefined, @@ -217,10 +309,25 @@ Array [ "type": "integer", "validationName": "integerRt", }, + Object { + "key": "transaction_name_groups", + "type": "text", + "validationName": "string", + }, Object { "key": "transaction_sample_rate", "type": "float", "validationName": "floatRt", }, + Object { + "key": "unnest_exceptions", + "type": "text", + "validationName": "string", + }, + Object { + "key": "use_path_as_transaction_name", + "type": "boolean", + "validationName": "(\\"true\\" | \\"false\\")", + }, ] `; diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts index 0e565e1d88030..4e0f37fc76f3b 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { captureBodyRt } from '../runtime_types/capture_body_rt'; import { logLevelRt } from '../runtime_types/log_level_rt'; +import { traceContinuationStrategyRt } from '../runtime_types/trace_continuation_strategy_rt'; import { RawSettingDefinition } from './types'; export const generalSettings: RawSettingDefinition[] = [ @@ -72,6 +73,29 @@ export const generalSettings: RawSettingDefinition[] = [ excludeAgents: ['js-base', 'rum-js', 'php'], }, + { + key: 'capture_body_content_types', + type: 'text', + defaultValue: + 'application/x-www-form-urlencoded*, text/*, application/json*, application/xml*', + label: i18n.translate( + 'xpack.apm.agentConfig.captureBodyContentTypes.label', + { + defaultMessage: 'Capture Body Content Types', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.captureBodyContentTypes.description', + { + defaultMessage: + 'Configures which content types should be recorded.\n' + + '\n' + + 'The defaults end with a wildcard so that content types like `text/plain; charset=utf-8` are captured as well.', + } + ), + includeAgents: ['java'], + }, + // Capture headers { key: 'capture_headers', @@ -90,6 +114,67 @@ export const generalSettings: RawSettingDefinition[] = [ excludeAgents: ['js-base', 'rum-js', 'nodejs', 'php'], }, + { + key: 'dedot_custom_metrics', + type: 'boolean', + defaultValue: 'true', + label: i18n.translate('xpack.apm.agentConfig.dedotCustomMetrics.label', { + defaultMessage: 'Dedot custom metrics', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.dedotCustomMetrics.description', + { + defaultMessage: + 'Replaces dots with underscores in the metric names for custom metrics.\n' + + '\n' + + 'WARNING: Setting this to `false` can lead to mapping conflicts as dots indicate nesting in Elasticsearch.\n' + + 'An example of when a conflict happens is two metrics with the name `foo` and `foo.bar`.\n' + + 'The first metric maps `foo` to a number and the second metric maps `foo` as an object.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'exit_span_min_duration', + type: 'duration', + defaultValue: '0ms', + min: '0ms', + label: i18n.translate('xpack.apm.agentConfig.exitSpanMinDuration.label', { + defaultMessage: 'Exit span min duration', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.exitSpanMinDuration.description', + { + defaultMessage: + 'Exit spans are spans that represent a call to an external service, like a database. If such calls are very short, they are usually not relevant and can be ignored.\n' + + '\n' + + 'NOTE: If a span propagates distributed tracing ids, it will not be ignored, even if it is shorter than the configured threshold. This is to ensure that no broken traces are recorded.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'ignore_message_queues', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.ignoreMessageQueues.label', { + defaultMessage: 'Ignore message queues', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.ignoreMessageQueues.description', + { + defaultMessage: + 'Used to filter out specific messaging queues/topics from being traced. \n' + + '\n' + + 'This property should be set to an array containing one or more strings.\n' + + 'When set, sends-to and receives-from the specified queues/topic will be ignored.', + } + ), + includeAgents: ['java'], + }, + // LOG_LEVEL { key: 'log_level', @@ -147,6 +232,68 @@ export const generalSettings: RawSettingDefinition[] = [ includeAgents: ['java'], }, + { + key: 'span_compression_enabled', + type: 'boolean', + defaultValue: 'true', + label: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionEnabled.label', + { + defaultMessage: 'Span compression enabled', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionEnabled.description', + { + defaultMessage: + 'Setting this option to true will enable span compression feature.\n' + + 'Span compression reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that some information such as DB statements of all the compressed spans will not be collected.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'span_compression_exact_match_max_duration', + type: 'duration', + defaultValue: '50ms', + min: '0ms', + label: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionExactMatchMaxDuration.label', + { + defaultMessage: 'Span compression exact match max duration', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionExactMatchMaxDuration.description', + { + defaultMessage: + 'Consecutive spans that are exact match and that are under this threshold will be compressed into a single composite span. This option does not apply to composite spans. This reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that the DB statements of all the compressed spans will not be collected.', + } + ), + includeAgents: ['java'], + }, + { + key: 'span_compression_same_kind_max_duration', + type: 'duration', + defaultValue: '0ms', + min: '0ms', + label: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionSameKindMaxDuration.label', + { + defaultMessage: 'Span compression same kind max duration', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.spanCompressionSameKindMaxDuration.description', + { + defaultMessage: + 'Consecutive spans to the same destination that are under this threshold will be compressed into a single composite span. This option does not apply to composite spans. This reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that the DB statements of all the compressed spans will not be collected.', + } + ), + includeAgents: ['java'], + }, + // SPAN_FRAMES_MIN_DURATION { key: 'span_frames_min_duration', @@ -184,6 +331,44 @@ export const generalSettings: RawSettingDefinition[] = [ includeAgents: ['java', 'dotnet', 'go'], }, + { + key: 'trace_continuation_strategy', + validation: traceContinuationStrategyRt, + type: 'select', + defaultValue: 'continue', + label: i18n.translate( + 'xpack.apm.agentConfig.traceContinuationStrategy.label', + { + defaultMessage: 'Trace continuation strategy', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.traceContinuationStrategy.description', + { + defaultMessage: + 'This option allows some control over how the APM agent handles W3C trace-context headers on incoming requests. By default, the `traceparent` and `tracestate` headers are used per W3C spec for distributed tracing. However, in certain cases it can be helpful to not use the incoming `traceparent` header. Some example use cases:\n' + + '\n' + + '* An Elastic-monitored service is receiving requests with `traceparent` headers from unmonitored services.\n' + + '* An Elastic-monitored service is publicly exposed, and does not want tracing data (trace-ids, sampling decisions) to possibly be spoofed by user requests.\n' + + '\n' + + 'Valid values are:\n' + + "* 'continue': The default behavior. An incoming `traceparent` value is used to continue the trace and determine the sampling decision.\n" + + "* 'restart': Always ignores the `traceparent` header of incoming requests. A new trace-id will be generated and the sampling decision will be made based on transaction_sample_rate. A span link will be made to the incoming `traceparent`.\n" + + "* 'restart_external': If an incoming request includes the `es` vendor flag in `tracestate`, then any `traceparent` will be considered internal and will be handled as described for 'continue' above. Otherwise, any `traceparent` is considered external and will be handled as described for 'restart' above.\n" + + '\n' + + 'Starting with Elastic Observability 8.2, span links are visible in trace views.\n' + + '\n' + + 'This option is case-insensitive.', + } + ), + options: [ + { text: 'continue', value: 'continue' }, + { text: 'restart', value: 'restart' }, + { text: 'restart_external', value: 'restart_external' }, + ], + includeAgents: ['java'], + }, + // Transaction max spans { key: 'transaction_max_spans', @@ -257,4 +442,76 @@ export const generalSettings: RawSettingDefinition[] = [ ), includeAgents: ['java', 'nodejs', 'python', 'dotnet', 'ruby', 'go'], }, + + { + key: 'transaction_ignore_user_agents', + type: 'text', + defaultValue: '', + label: i18n.translate( + 'xpack.apm.agentConfig.transactionIgnoreUserAgents.label', + { + defaultMessage: 'Transaction ignore user agents', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.transactionIgnoreUserAgents.description', + { + defaultMessage: + 'Used to restrict requests from certain User-Agents from being instrumented.\n' + + '\n' + + 'When an incoming HTTP request is detected,\n' + + 'the User-Agent from the request headers will be tested against each element in this list.\n' + + 'Example: `curl/*`, `*pingdom*`', + } + ), + includeAgents: ['java'], + }, + + { + key: 'use_path_as_transaction_name', + type: 'boolean', + defaultValue: 'false', + label: i18n.translate( + 'xpack.apm.agentConfig.usePathAsTransactionName.label', + { + defaultMessage: 'Use path as transaction name', + } + ), + description: i18n.translate( + 'xpack.apm.agentConfig.usePathAsTransactionName.description', + { + defaultMessage: + 'If set to `true`,\n' + + 'transaction names of unsupported or partially-supported frameworks will be in the form of `$method $path` instead of just `$method unknown route`.\n' + + '\n' + + 'WARNING: If your URLs contain path parameters like `/user/$userId`,\n' + + 'you should be very careful when enabling this flag,\n' + + 'as it can lead to an explosion of transaction groups.\n' + + 'Take a look at the `transaction_name_groups` option on how to mitigate this problem by grouping URLs together.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'transaction_name_groups', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.transactionNameGroups.label', { + defaultMessage: 'Transaction name groups', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.transactionNameGroups.description', + { + defaultMessage: + 'With this option,\n' + + 'you can group transaction names that contain dynamic parts with a wildcard expression.\n' + + 'For example,\n' + + 'the pattern `GET /user/*/cart` would consolidate transactions,\n' + + 'such as `GET /users/42/cart` and `GET /users/73/cart` into a single transaction name `GET /users/*/cart`,\n' + + 'hence reducing the transaction name cardinality.', + } + ), + includeAgents: ['java'], + }, ]; diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts index c9d33d37b6601..e41a3b6896ee8 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts @@ -43,134 +43,150 @@ describe('filterByAgent', () => { describe('options per agent', () => { it('go', () => { - expect(getSettingKeysForAgent('go')).toEqual([ - 'capture_body', - 'capture_headers', - 'log_level', - 'recording', - 'sanitize_field_names', - 'span_frames_min_duration', - 'stack_trace_limit', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('go')).toEqual( + expect.arrayContaining([ + 'capture_body', + 'capture_headers', + 'log_level', + 'recording', + 'sanitize_field_names', + 'span_frames_min_duration', + 'stack_trace_limit', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('java', () => { - expect(getSettingKeysForAgent('java')).toEqual([ - 'api_request_size', - 'api_request_time', - 'capture_body', - 'capture_headers', - 'circuit_breaker_enabled', - 'enable_log_correlation', - 'log_level', - 'profiling_inferred_spans_enabled', - 'profiling_inferred_spans_excluded_classes', - 'profiling_inferred_spans_included_classes', - 'profiling_inferred_spans_min_duration', - 'profiling_inferred_spans_sampling_interval', - 'recording', - 'sanitize_field_names', - 'server_timeout', - 'span_frames_min_duration', - 'stack_trace_limit', - 'stress_monitor_cpu_duration_threshold', - 'stress_monitor_gc_relief_threshold', - 'stress_monitor_gc_stress_threshold', - 'stress_monitor_system_cpu_relief_threshold', - 'stress_monitor_system_cpu_stress_threshold', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('java')).toEqual( + expect.arrayContaining([ + 'api_request_size', + 'api_request_time', + 'capture_body', + 'capture_headers', + 'circuit_breaker_enabled', + 'enable_log_correlation', + 'log_level', + 'profiling_inferred_spans_enabled', + 'profiling_inferred_spans_excluded_classes', + 'profiling_inferred_spans_included_classes', + 'profiling_inferred_spans_min_duration', + 'profiling_inferred_spans_sampling_interval', + 'recording', + 'sanitize_field_names', + 'server_timeout', + 'span_frames_min_duration', + 'stack_trace_limit', + 'stress_monitor_cpu_duration_threshold', + 'stress_monitor_gc_relief_threshold', + 'stress_monitor_gc_stress_threshold', + 'stress_monitor_system_cpu_relief_threshold', + 'stress_monitor_system_cpu_stress_threshold', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('js-base', () => { - expect(getSettingKeysForAgent('js-base')).toEqual([ - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('js-base')).toEqual( + expect.arrayContaining(['transaction_sample_rate']) + ); }); it('rum-js', () => { - expect(getSettingKeysForAgent('rum-js')).toEqual([ - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('rum-js')).toEqual( + expect.arrayContaining(['transaction_sample_rate']) + ); }); it('nodejs', () => { - expect(getSettingKeysForAgent('nodejs')).toEqual([ - 'capture_body', - 'log_level', - 'sanitize_field_names', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('nodejs')).toEqual( + expect.arrayContaining([ + 'capture_body', + 'log_level', + 'sanitize_field_names', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('python', () => { - expect(getSettingKeysForAgent('python')).toEqual([ - 'api_request_size', - 'api_request_time', - 'capture_body', - 'capture_headers', - 'log_level', - 'recording', - 'sanitize_field_names', - 'span_frames_min_duration', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('python')).toEqual( + expect.arrayContaining([ + 'api_request_size', + 'api_request_time', + 'capture_body', + 'capture_headers', + 'log_level', + 'recording', + 'sanitize_field_names', + 'span_frames_min_duration', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('dotnet', () => { - expect(getSettingKeysForAgent('dotnet')).toEqual([ - 'capture_body', - 'capture_headers', - 'log_level', - 'recording', - 'sanitize_field_names', - 'span_frames_min_duration', - 'stack_trace_limit', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('dotnet')).toEqual( + expect.arrayContaining([ + 'capture_body', + 'capture_headers', + 'log_level', + 'recording', + 'sanitize_field_names', + 'span_frames_min_duration', + 'stack_trace_limit', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('ruby', () => { - expect(getSettingKeysForAgent('ruby')).toEqual([ - 'api_request_size', - 'api_request_time', - 'capture_body', - 'capture_headers', - 'log_level', - 'recording', - 'sanitize_field_names', - 'span_frames_min_duration', - 'transaction_ignore_urls', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('ruby')).toEqual( + expect.arrayContaining([ + 'api_request_size', + 'api_request_time', + 'capture_body', + 'capture_headers', + 'log_level', + 'recording', + 'sanitize_field_names', + 'span_frames_min_duration', + 'transaction_ignore_urls', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('php', () => { - expect(getSettingKeysForAgent('php')).toEqual([ - 'log_level', - 'recording', - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent('php')).toEqual( + expect.arrayContaining([ + 'log_level', + 'recording', + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); it('"All" services (no agent name)', () => { - expect(getSettingKeysForAgent(undefined)).toEqual([ - 'transaction_max_spans', - 'transaction_sample_rate', - ]); + expect(getSettingKeysForAgent(undefined)).toEqual( + expect.arrayContaining([ + 'transaction_max_spans', + 'transaction_sample_rate', + ]) + ); }); }); }); diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts index dc4eb89cf7dec..694eef7885b31 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts @@ -238,4 +238,95 @@ export const javaSettings: RawSettingDefinition[] = [ ), includeAgents: ['java'], }, + + { + key: 'capture_jmx_metrics', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.captureJmxMetrics.label', { + defaultMessage: 'Capture JMX metrics', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.captureJmxMetrics.description', + { + defaultMessage: + 'Report metrics from JMX to the APM Server\n' + + '\n' + + 'Can contain multiple comma separated JMX metric definitions:\n' + + '\n' + + '`object_name[] attribute[:metric_name=]`\n' + + '\n' + + 'See the Java agent documentation for more details.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'ignore_exceptions', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.ignoreExceptions.label', { + defaultMessage: 'Ignore exceptions', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.ignoreExceptions.description', + { + defaultMessage: + 'A list of exceptions that should be ignored and not reported as errors.\n' + + 'This allows to ignore exceptions thrown in regular control flow that are not actual errors.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'trace_methods', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.traceMethods.label', { + defaultMessage: 'Trace methods', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.traceMethods.description', + { + defaultMessage: + 'A list of methods for which to create a transaction or span.\n' + + '\n' + + 'If you want to monitor a large number of methods,\n' + + 'use `profiling_inferred_spans_enabled`.\n' + + '\n' + + 'This works by instrumenting each matching method to include code that creates a span for the method.\n' + + 'While creating a span is quite cheap in terms of performance,\n' + + 'instrumenting a whole code base or a method which is executed in a tight loop leads to significant overhead.\n' + + '\n' + + 'NOTE: Only use wildcards if necessary.\n' + + 'The more methods you match the more overhead will be caused by the agent.\n' + + 'Also note that there is a maximum amount of spans per transaction `transaction_max_spans`.\n' + + '\n' + + 'See the Java agent documentation for more details.', + } + ), + includeAgents: ['java'], + }, + + { + key: 'unnest_exceptions', + type: 'text', + defaultValue: '', + label: i18n.translate('xpack.apm.agentConfig.unnestExceptions.label', { + defaultMessage: 'Unnest exceptions', + }), + description: i18n.translate( + 'xpack.apm.agentConfig.unnestExceptions.description', + { + defaultMessage: + 'When reporting exceptions,\n' + + 'un-nests the exceptions matching the wildcard pattern.\n' + + "This can come in handy for Spring's `org.springframework.web.util.NestedServletException`,\n" + + 'for example.', + } + ), + includeAgents: ['java'], + }, ]; diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts index f29bf3c607e86..bbd5755e3afb9 100644 --- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts @@ -147,6 +147,7 @@ export const USER_AGENT_DEVICE = 'user_agent.device.name'; export const USER_AGENT_OS = 'user_agent.os.name'; export const FAAS_ID = 'faas.id'; +export const FAAS_NAME = 'faas.name'; export const FAAS_COLDSTART = 'faas.coldstart'; export const FAAS_TRIGGER_TYPE = 'faas.trigger.type'; export const FAAS_DURATION = 'faas.duration'; diff --git a/x-pack/plugins/apm/common/serverless.test.ts b/x-pack/plugins/apm/common/serverless.test.ts new file mode 100644 index 0000000000000..5473e9d735d86 --- /dev/null +++ b/x-pack/plugins/apm/common/serverless.test.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 { getServerlessFunctionNameFromId } from './serverless'; + +describe('getServerlessFunctionNameFromId', () => { + it('returns serverlessId when regex does not match', () => { + expect(getServerlessFunctionNameFromId('foo')).toEqual('foo'); + }); + + it('returns correct serverless function name', () => { + expect( + getServerlessFunctionNameFromId( + 'arn:aws:lambda:us-west-2:123456789012:function:my-function' + ) + ).toEqual('my-function'); + expect( + getServerlessFunctionNameFromId( + 'arn:aws:lambda:us-west-2:123456789012:function:my:function' + ) + ).toEqual('my:function'); + }); +}); diff --git a/x-pack/plugins/apm/common/serverless.ts b/x-pack/plugins/apm/common/serverless.ts new file mode 100644 index 0000000000000..5e91cb04d868d --- /dev/null +++ b/x-pack/plugins/apm/common/serverless.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. + */ + +/** + * Gets the serverless function name from serverless id. + * Serverless id example: arn:aws:lambda:us-west-2:123456789012:function:my-function + * The function name is the last part after "function:" + */ +const serverlessIdRegex = /function:(.*)/; +export function getServerlessFunctionNameFromId(serverlessId: string) { + const match = serverlessIdRegex.exec(serverlessId); + return match ? match[1] : serverlessId; +} diff --git a/x-pack/plugins/apm/common/tutorial/instructions/apm_agent_instructions.ts b/x-pack/plugins/apm/common/tutorial/instructions/apm_agent_instructions.ts index b1c5fc79816ac..9406942f85179 100644 --- a/x-pack/plugins/apm/common/tutorial/instructions/apm_agent_instructions.ts +++ b/x-pack/plugins/apm/common/tutorial/instructions/apm_agent_instructions.ts @@ -519,10 +519,12 @@ export const createDotNetAgentInstructions = ( defaultMessage: 'In case you don’t pass an `IConfiguration` instance to the agent (e.g. in case of non ASP.NET Core applications) \ you can also configure the agent through environment variables. \n \ - See [the documentation]({documentationLink}) for advanced usage.', + See [the documentation]({documentationLink}) for advanced usage, including the [Profiler Auto instrumentation]({profilerLink}) quick start.', values: { documentationLink: '{config.docs.base_url}guide/en/apm/agent/dotnet/current/configuration.html', + profilerLink: + '{config.docs.base_url}guide/en/apm/agent/dotnet/current/setup-auto-instrumentation.html#setup-auto-instrumentation', }, } ), diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts index 2efebecf25756..3fdeff2e406a8 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts @@ -79,6 +79,8 @@ describe('Storage Explorer', () => { it('has a list of summary stats', () => { cy.contains('Total APM size'); + cy.contains('Disk space used'); + cy.contains('Incremental APM size'); cy.contains('Daily data generation'); cy.contains('Traces per minute'); cy.contains('Number of services'); @@ -200,6 +202,7 @@ describe('Storage Explorer', () => { cy.contains('Service storage details'); cy.getByTestSubj('storageExplorerTimeseriesChart'); cy.getByTestSubj('serviceStorageDetailsTable'); + cy.getByTestSubj('storageExplorerIndicesStatsTable'); }); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts index 9e6e0189e636c..013296d815a58 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts @@ -21,7 +21,7 @@ Cypress.Commands.add('loginAsEditorUser', () => { Cypress.Commands.add('loginAsMonitorUser', () => { return cy.loginAs({ - username: ApmUsername.apmMonitorIndices, + username: ApmUsername.apmMonitorClusterAndIndices, password: 'changeme', }); }); diff --git a/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx b/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx index 54d455519329c..00c768fc1abbf 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/use_failed_transactions_correlations.test.tsx @@ -108,7 +108,7 @@ function wrapper({ describe('useFailedTransactionsCorrelations', () => { beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); // Running all pending timers and switching to real timers using Jest afterEach(() => { diff --git a/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.test.tsx b/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.test.tsx index b63327901a028..a09530960589c 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.test.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/use_latency_correlations.test.tsx @@ -102,7 +102,7 @@ function wrapper({ describe('useLatencyCorrelations', () => { beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { jest.useRealTimers(); diff --git a/x-pack/plugins/apm/public/components/app/metrics/index.tsx b/x-pack/plugins/apm/public/components/app/metrics/index.tsx index 3149a13b940f8..0463df3778e87 100644 --- a/x-pack/plugins/apm/public/components/app/metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/metrics/index.tsx @@ -6,17 +6,30 @@ */ import React from 'react'; -import { isJavaAgentName, isJRubyAgent } from '../../../../common/agent_name'; +import { + isJavaAgentName, + isJRubyAgent, + isServerlessAgent, +} from '../../../../common/agent_name'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { ServerlessMetrics } from './serverless_metrics'; import { ServiceMetrics } from './service_metrics'; import { JvmMetricsOverview } from './jvm_metrics_overview'; export function Metrics() { const { agentName, runtimeName } = useApmServiceContext(); + const isServerless = isServerlessAgent(runtimeName); - if (isJavaAgentName(agentName) || isJRubyAgent(agentName, runtimeName)) { + if ( + !isServerless && + (isJavaAgentName(agentName) || isJRubyAgent(agentName, runtimeName)) + ) { return ; } + if (isServerless) { + return ; + } + return ; } diff --git a/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/index.tsx new file mode 100644 index 0000000000000..439192503244f --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/index.tsx @@ -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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { ServerlessFunctions } from './serverless_functions'; +import { ServerlessSummary } from './serverless_summary'; +import { ServerlessActiveInstances } from './serverless_active_instances'; +import { ServerlessMetricsCharts } from './serverless_metrics_charts'; +import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context'; + +interface Props { + serverlessId?: string; +} + +export function ServerlessMetrics({ serverlessId }: Props) { + return ( + + + + + {!serverlessId && ( + + + + )} + + + + + + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_active_instances.tsx b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_active_instances.tsx new file mode 100644 index 0000000000000..9c44d472c5b70 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_active_instances.tsx @@ -0,0 +1,223 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + euiPaletteColorBlind, + EuiPanel, + EuiTitle, + PropertySort, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useMemo } from 'react'; +import { + asDynamicBytes, + asInteger, + asMillisecondDuration, +} from '../../../../../common/utils/formatters'; +import { Coordinate, TimeSeries } from '../../../../../typings/timeseries'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../hooks/use_time_range'; +import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; +import { TimeseriesChart } from '../../../shared/charts/timeseries_chart'; +import { ListMetric } from '../../../shared/list_metric'; +import { ServerlessFunctionNameLink } from './serverless_function_name_link'; + +type ServerlessActiveInstances = + APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances'>; + +const palette = euiPaletteColorBlind({ rotations: 2 }); + +interface Props { + serverlessId?: string; +} + +export function ServerlessActiveInstances({ serverlessId }: Props) { + const { + query: { environment, kuery, rangeFrom, rangeTo }, + } = useApmParams('/services/{serviceName}/metrics'); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { serviceName } = useApmServiceContext(); + + const { data = { activeInstances: [], timeseries: [] }, status } = useFetcher( + (callApmApi) => { + if (!start || !end) { + return undefined; + } + return callApmApi( + 'GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances', + { + params: { + path: { + serviceName, + }, + query: { + kuery, + environment, + start, + end, + serverlessId, + }, + }, + } + ); + }, + [kuery, environment, serviceName, start, end, serverlessId] + ); + + const isLoading = status === FETCH_STATUS.LOADING; + + const columns: Array< + EuiBasicTableColumn + > = [ + { + field: 'serverlessFunctionName', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.activeInstances.functionName', + { defaultMessage: 'Function name' } + ), + sortable: true, + truncateText: true, + render: (_, item) => { + return ( + + ); + }, + }, + { + field: 'activeInstanceName', + name: i18n.translate('xpack.apm.serverlessMetrics.activeInstances.name', { + defaultMessage: 'Name', + }), + sortable: true, + }, + { + field: 'serverlessDurationAvg', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.serverlessFunctions.functionDuration', + { defaultMessage: 'Function duration' } + ), + sortable: true, + render: (_, { serverlessDurationAvg, timeseries }) => { + return ( + + ); + }, + }, + { + field: 'billedDurationAvg', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.activeInstances.billedDuration', + { defaultMessage: 'Billed duration' } + ), + sortable: true, + render: (_, { billedDurationAvg, timeseries }) => { + return ( + + ); + }, + }, + { + field: 'avgMemoryUsed', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.activeInstances.memoryUsageAvg', + { defaultMessage: 'Memory usage avg.' } + ), + sortable: true, + render: (_, { avgMemoryUsed }) => { + return asDynamicBytes(avgMemoryUsed); + }, + }, + { + field: 'memorySize', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.activeInstances.memorySize', + { defaultMessage: 'Memory size' } + ), + sortable: true, + render: (_, { memorySize }) => { + return asDynamicBytes(memorySize); + }, + }, + ]; + + const sorting = useMemo( + () => ({ + sort: { + field: 'serverlessDurationAvg', + direction: 'desc', + } as PropertySort, + }), + [] + ); + + const charts: Array> = useMemo( + () => [ + { + title: i18n.translate( + 'xpack.apm.serverlessMetrics.activeInstances.title', + { defaultMessage: 'Active instances' } + ), + data: data.timeseries, + type: 'bar', + color: palette[2], + }, + ], + [data.timeseries] + ); + + return ( + + + + +

    + {i18n.translate( + 'xpack.apm.serverlessMetrics.activeInstances.title', + { defaultMessage: 'Active instances' } + )} +

    +
    +
    + + + + + + +
    +
    + ); +} diff --git a/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_function_name_link.tsx b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_function_name_link.tsx new file mode 100644 index 0000000000000..83fb9a5beee32 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_function_name_link.tsx @@ -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 { EuiLink } from '@elastic/eui'; +import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import React from 'react'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useApmRouter } from '../../../../hooks/use_apm_router'; +import { truncate } from '../../../../utils/style'; + +const StyledLink = euiStyled(EuiLink)`${truncate('100%')};`; + +interface Props { + serverlessFunctionName: string; + serverlessId: string; +} + +export function ServerlessFunctionNameLink({ + serverlessFunctionName, + serverlessId, +}: Props) { + const { serviceName } = useApmServiceContext(); + const { query } = useApmParams('/services/{serviceName}/metrics'); + const { link } = useApmRouter(); + return ( + + {serverlessFunctionName} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_functions.tsx b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_functions.tsx new file mode 100644 index 0000000000000..11c2f9056b117 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_functions.tsx @@ -0,0 +1,178 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiPanel, + EuiTitle, + PropertySort, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useMemo } from 'react'; +import { + asDynamicBytes, + asMillisecondDuration, +} from '../../../../../common/utils/formatters'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../hooks/use_time_range'; +import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; +import { ServerlessFunctionNameLink } from './serverless_function_name_link'; + +type ServerlessFunctionOverview = + APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview'>['serverlessFunctionsOverview'][0]; + +export function ServerlessFunctions() { + const { + query: { environment, kuery, rangeFrom, rangeTo }, + } = useApmParams('/services/{serviceName}/metrics'); + + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { serviceName } = useApmServiceContext(); + + const { data = { serverlessFunctionsOverview: [] }, status } = useFetcher( + (callApmApi) => { + if (!start || !end) { + return undefined; + } + return callApmApi( + 'GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview', + { + params: { + path: { + serviceName, + }, + query: { + kuery, + environment, + start, + end, + }, + }, + } + ); + }, + [kuery, environment, serviceName, start, end] + ); + + const columns: Array> = [ + { + field: 'serverlessFunctionName', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.serverlessFunctions.functionName', + { defaultMessage: 'Function name' } + ), + sortable: true, + truncateText: true, + render: (_, item) => { + return ( + + ); + }, + }, + { + field: 'serverlessDurationAvg', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.serverlessFunctions.functionDuration', + { defaultMessage: 'Function duration' } + ), + sortable: true, + render: (_, { serverlessDurationAvg }) => { + return asMillisecondDuration(serverlessDurationAvg); + }, + }, + { + field: 'billedDurationAvg', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.serverlessFunctions.billedDuration', + { defaultMessage: 'Billed duration' } + ), + sortable: true, + render: (_, { billedDurationAvg }) => { + return asMillisecondDuration(billedDurationAvg); + }, + }, + { + field: 'avgMemoryUsed', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.serverlessFunctions.memoryUsageAvg', + { defaultMessage: 'Memory usage avg.' } + ), + sortable: true, + render: (_, { avgMemoryUsed }) => { + return asDynamicBytes(avgMemoryUsed); + }, + }, + { + field: 'memorySize', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.serverlessFunctions.memorySize', + { defaultMessage: 'Memory size' } + ), + sortable: true, + render: (_, { memorySize }) => { + return asDynamicBytes(memorySize); + }, + }, + { + field: 'coldStartCount', + name: i18n.translate( + 'xpack.apm.serverlessMetrics.serverlessFunctions.coldStart', + { defaultMessage: 'Cold start' } + ), + sortable: true, + }, + ]; + + const isLoading = status === FETCH_STATUS.LOADING; + + const sorting = useMemo( + () => ({ + sort: { + field: 'serverlessDurationAvg', + direction: 'desc', + } as PropertySort, + }), + [] + ); + + return ( + + + + + + +

    + {i18n.translate( + 'xpack.apm.serverlessMetrics.serverlessFunctions.title', + { defaultMessage: 'Lambda functions' } + )} +

    +
    +
    +
    +
    + + + +
    +
    + ); +} diff --git a/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_metrics_charts.tsx b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_metrics_charts.tsx new file mode 100644 index 0000000000000..ca5ea070b995c --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_metrics_charts.tsx @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import { isEmpty, keyBy } from 'lodash'; +import React from 'react'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../hooks/use_time_range'; +import { MetricsChart } from '../../../shared/charts/metrics_chart'; + +interface Props { + serverlessId?: string; +} + +const INITIAL_STATE = { + firstLineCharts: [], + secondLineCharts: [], +}; + +export function ServerlessMetricsCharts({ serverlessId }: Props) { + const { + query: { environment, kuery, rangeFrom, rangeTo }, + } = useApmParams('/services/{serviceName}/metrics'); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { serviceName } = useApmServiceContext(); + + const { data = INITIAL_STATE, status } = useFetcher( + (callApmApi) => { + if (!start || !end) { + return undefined; + } + return callApmApi( + 'GET /internal/apm/services/{serviceName}/metrics/serverless/charts', + { + params: { + path: { + serviceName, + }, + query: { + kuery, + environment, + start, + end, + serverlessId, + }, + }, + } + ).then((resp) => { + const chartsByKey = keyBy(resp.charts, 'key'); + if (isEmpty(chartsByKey)) { + return { firstLineCharts: [], secondLineCharts: [] }; + } + + return { + firstLineCharts: [ + chartsByKey.avg_duration, + chartsByKey.cold_start_duration, + chartsByKey.cold_start_count, + ], + secondLineCharts: [ + chartsByKey.compute_usage, + chartsByKey.memory_usage_chart, + ], + }; + }); + }, + [kuery, environment, serviceName, start, end, serverlessId] + ); + + return ( + + + + {data.firstLineCharts.map((chart) => ( + + + + + + ))} + + + + + {data.secondLineCharts.map((chart) => ( + + + + + + ))} + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_summary.tsx b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_summary.tsx new file mode 100644 index 0000000000000..f6d93cb9cd809 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/metrics/serverless_metrics/serverless_summary.tsx @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, + EuiLink, + EuiPanel, + EuiSpacer, + EuiStat, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import styled from 'styled-components'; +import { + asMillisecondDuration, + asPercent, +} from '../../../../../common/utils/formatters'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useBreakpoints } from '../../../../hooks/use_breakpoints'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../../hooks/use_time_range'; + +interface Props { + serverlessId?: string; +} + +const CentralizedContainer = styled.div` + display: flex; + align-items: center; +`; + +const Border = styled.div` + height: 55px; + border-right: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; +`; + +function VerticalRule() { + return ( + + + + ); +} + +export function ServerlessSummary({ serverlessId }: Props) { + const breakpoints = useBreakpoints(); + const { + query: { environment, kuery, rangeFrom, rangeTo }, + } = useApmParams('/services/{serviceName}/metrics'); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { serviceName } = useApmServiceContext(); + + const { data, status } = useFetcher( + (callApmApi) => { + if (!start || !end) { + return undefined; + } + return callApmApi( + 'GET /internal/apm/services/{serviceName}/metrics/serverless/summary', + { + params: { + path: { + serviceName, + }, + query: { + kuery, + environment, + start, + end, + serverlessId, + }, + }, + } + ); + }, + [kuery, environment, serviceName, start, end, serverlessId] + ); + + const showVerticalRule = !breakpoints.isSmall; + const isLoading = status === FETCH_STATUS.LOADING; + + return ( + + + + +

    + {i18n.translate('xpack.apm.serverlessMetrics.summary.title', { + defaultMessage: 'Summary', + })} +

    +
    +
    + + + {i18n.translate('xpack.apm.serverlessMetrics.summary.feedback', { + defaultMessage: 'Send feedback', + })} + + +
    + + + + + + {showVerticalRule && } + + + + + + + + + + {showVerticalRule && } + +
    + ); +} diff --git a/x-pack/plugins/apm/public/components/app/metrics_details/index.tsx b/x-pack/plugins/apm/public/components/app/metrics_details/index.tsx index 4adbb25c26d1e..13082d41f5970 100644 --- a/x-pack/plugins/apm/public/components/app/metrics_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/metrics_details/index.tsx @@ -5,8 +5,21 @@ * 2.0. */ import React from 'react'; +import { isServerlessAgent } from '../../../../common/agent_name'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { useApmParams } from '../../../hooks/use_apm_params'; +import { ServerlessMetricsDetails } from './serverless_metrics_details'; import { ServiceNodeMetrics } from './service_node_metrics'; export function MetricsDetails() { - return ; + const { + path: { id }, + } = useApmParams('/services/{serviceName}/metrics/{id}'); + const { runtimeName } = useApmServiceContext(); + + if (isServerlessAgent(runtimeName)) { + return ; + } + + return ; } diff --git a/x-pack/plugins/apm/public/components/app/metrics_details/serverless_metrics_details/index.tsx b/x-pack/plugins/apm/public/components/app/metrics_details/serverless_metrics_details/index.tsx new file mode 100644 index 0000000000000..7b80ed857134d --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/metrics_details/serverless_metrics_details/index.tsx @@ -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 React, { useMemo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import { ServerlessMetrics } from '../../metrics/serverless_metrics'; +import { getServerlessFunctionNameFromId } from '../../../../../common/serverless'; +import { useBreadcrumb } from '../../../../context/breadcrumbs/use_breadcrumb'; +import { useApmRouter } from '../../../../hooks/use_apm_router'; +import { useApmParams } from '../../../../hooks/use_apm_params'; + +interface Props { + serverlessId: string; +} + +export function ServerlessMetricsDetails({ serverlessId }: Props) { + const apmRouter = useApmRouter(); + const { path, query } = useApmParams('/services/{serviceName}/metrics/{id}'); + + const serverlessFunctionName = useMemo( + () => getServerlessFunctionNameFromId(serverlessId), + [serverlessId] + ); + + useBreadcrumb( + () => ({ + title: serverlessFunctionName, + href: apmRouter.link('/services/{serviceName}/metrics/{id}', { + path, + query, + }), + }), + [apmRouter, path, query, serverlessFunctionName] + ); + + return ( + + + +

    {serverlessFunctionName}

    +
    +
    + + + +
    + ); +} diff --git a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.test.tsx b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.test.tsx index 9356cab2d1f9c..e8deea04a71e0 100644 --- a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.test.tsx @@ -16,7 +16,7 @@ describe('ServiceNodeMetrics', () => { expect(() => shallow( - + ) ).not.toThrowError(); diff --git a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx index cc0703c036780..b78542f291f72 100644 --- a/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/metrics_details/service_node_metrics/index.tsx @@ -46,15 +46,16 @@ const Truncate = euiStyled.span` ${truncate(unit * 12)} `; -export function ServiceNodeMetrics() { +interface Props { + serviceNodeName: string; +} + +export function ServiceNodeMetrics({ serviceNodeName }: Props) { const { agentName, serviceName } = useApmServiceContext(); const apmRouter = useApmRouter(); - const { - path: { id: serviceNodeName }, - query, - } = useApmParams('/services/{serviceName}/metrics/{id}'); + const { query } = useApmParams('/services/{serviceName}/metrics/{id}'); const { environment, kuery, rangeFrom, rangeTo } = query; diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/get_storage_explorer_links.ts b/x-pack/plugins/apm/public/components/app/storage_explorer/get_storage_explorer_links.ts new file mode 100644 index 0000000000000..1736cd852a417 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/get_storage_explorer_links.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 { CoreStart } from '@kbn/core-lifecycle-browser'; + +export function getIndexManagementHref(core: CoreStart) { + return core.application.getUrlForApp('management', { + path: '/data/index_management/data_streams', + }); +} + +export function getStorageExplorerFeedbackHref() { + return 'https://ela.st/feedback-storage-explorer'; +} + +export function getKibanaAdvancedSettingsHref(core: CoreStart) { + return core.application.getUrlForApp('management', { + path: '/kibana/settings?query=category:(observability)', + }); +} diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx index 5b8c044d2276c..ef0c198000ddd 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/index.tsx @@ -12,8 +12,14 @@ import { EuiSpacer, EuiEmptyPrompt, EuiLoadingSpinner, + EuiCallOut, + EuiLink, + EuiButton, + EuiPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { IndexLifecyclePhaseSelect } from './index_lifecycle_phase_select'; import { ServicesTable } from './services_table'; import { SearchBar } from '../../shared/search_bar'; @@ -22,18 +28,51 @@ import { PermissionDenied } from './prompts/permission_denied'; import { useFetcher, FETCH_STATUS } from '../../../hooks/use_fetcher'; import { SummaryStats } from './summary_stats'; import { ApmEnvironmentFilter } from '../../shared/environment_filter'; +import { TipsAndResources } from './resources/tips_and_resources'; +import { useLocalStorage } from '../../../hooks/use_local_storage'; +import { getKibanaAdvancedSettingsHref } from './get_storage_explorer_links'; -const INITIAL_DATA = { hasPrivileges: false }; +type CalloutType = 'crossClusterSearch' | 'optimizePerformance'; + +const CALLOUT_DISMISS_INITIAL_STATE: Record = { + crossClusterSearch: false, + optimizePerformance: false, +}; + +const dismissButtonText = i18n.translate( + 'xpack.apm.storageExplorer.callout.dimissButton', + { + defaultMessage: 'Dismiss', + } +); export function StorageExplorer() { - const { data: { hasPrivileges } = INITIAL_DATA, status } = useFetcher( + const { core } = useApmPluginContext(); + + const [calloutDismissed, setCalloutDismissed] = useLocalStorage( + 'apm.storageExplorer.calloutDismissed', + CALLOUT_DISMISS_INITIAL_STATE + ); + + const { data: hasPrivilegesData, status: hasPrivilegesStatus } = useFetcher( (callApmApi) => { return callApmApi('GET /internal/apm/storage_explorer/privileges'); }, [] ); - const loading = status === FETCH_STATUS.LOADING; + const { data: isCrossClusterSearchData } = useFetcher( + (callApmApi) => { + if (!calloutDismissed.crossClusterSearch) { + return callApmApi( + 'GET /internal/apm/storage_explorer/is_cross_cluster_search' + ); + } + }, + [calloutDismissed] + ); + + const loading = hasPrivilegesStatus === FETCH_STATUS.LOADING; if (loading) { return ( @@ -51,7 +90,7 @@ export function StorageExplorer() { ); } - if (!hasPrivileges) { + if (!hasPrivilegesData?.hasPrivileges) { return ; } @@ -67,11 +106,94 @@ export function StorageExplorer() { + + {!calloutDismissed.optimizePerformance && ( + +

    + + {i18n.translate( + 'xpack.apm.storageExplorer.longLoadingTimeCalloutLink', + { + defaultMessage: 'Kibana advanced settings', + } + )} + + ), + }} + /> +

    + + setCalloutDismissed({ + ...calloutDismissed, + optimizePerformance: true, + }) + } + > + {dismissButtonText} + +
    + )} + + {!calloutDismissed.crossClusterSearch && + isCrossClusterSearchData?.isCrossClusterSearch && ( + <> + + +

    + {i18n.translate( + 'xpack.apm.storageExplorer.crossClusterSearchCalloutText', + { + defaultMessage: + 'While getting document count works with cross-cluster search, index statistics such as size are only displayed for data that are stored in this cluster.', + } + )} +

    + + setCalloutDismissed({ + ...calloutDismissed, + crossClusterSearch: true, + }) + } + > + {dismissButtonText} + +
    + + )} + + - + + + + + - + ); } diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/resources/tips_and_resources.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/resources/tips_and_resources.tsx new file mode 100644 index 0000000000000..8de6d7d3e56d7 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/resources/tips_and_resources.tsx @@ -0,0 +1,203 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { + EuiAccordion, + EuiPanel, + EuiFlexItem, + EuiTitle, + EuiButton, + EuiCard, + EuiIcon, + EuiListGroup, + EuiFlexGroup, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useApmRouter } from '../../../../hooks/use_apm_router'; +import { useApmParams } from '../../../../hooks/use_apm_params'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { + getIndexManagementHref, + getStorageExplorerFeedbackHref, +} from '../get_storage_explorer_links'; + +export function TipsAndResources() { + const router = useApmRouter(); + const { core } = useApmPluginContext(); + const { docLinks } = core; + + const { + query: { rangeFrom, rangeTo, environment, kuery, comparisonEnabled }, + } = useApmParams('/storage-explorer'); + + const cards = [ + { + icon: 'beaker', + title: i18n.translate( + 'xpack.apm.storageExplorer.resources.errorMessages.title', + { + defaultMessage: 'Reduce transactions', + } + ), + description: i18n.translate( + 'xpack.apm.storageExplorer.resources.errorMessages.description', + { + defaultMessage: + 'Configure a more aggressive transaction sampling policy. Transaction sampling lowers the amount of data ingested without negatively impacting the usefulness of that data.', + } + ), + href: docLinks.links.apm.transactionSampling, + }, + { + icon: 'visLine', + title: i18n.translate( + 'xpack.apm.storageExplorer.resources.compressedSpans.title', + { + defaultMessage: 'Reduce spans', + } + ), + description: i18n.translate( + 'xpack.apm.storageExplorer.resources.compressedSpans.description', + { + defaultMessage: + 'Enable span compression. Span compression saves on data and transfer costs by compressing multiple similar spans into a single span.', + } + ), + href: docLinks.links.apm.spanCompression, + }, + { + icon: 'indexEdit', + title: i18n.translate( + 'xpack.apm.storageExplorer.resources.samplingRate.title', + { + defaultMessage: 'Manage the index lifecycle', + } + ), + description: i18n.translate( + 'xpack.apm.storageExplorer.resources.samplingRate.description', + { + defaultMessage: + 'Customize your index lifecycle policies. Index lifecycle policies allow you to manage indices according to your performance, resiliency, and retention requirements.', + } + ), + href: docLinks.links.apm.indexLifecycleManagement, + }, + ]; + + const resourcesListItems = [ + { + label: i18n.translate( + 'xpack.apm.storageExplorer.resources.indexManagement', + { + defaultMessage: 'Index management', + } + ), + href: getIndexManagementHref(core), + iconType: 'indexEdit', + }, + { + label: i18n.translate( + 'xpack.apm.storageExplorer.resources.serviceInventory', + { + defaultMessage: 'Service inventory', + } + ), + href: router.link('/services', { + query: { + rangeFrom, + rangeTo, + environment, + comparisonEnabled, + kuery, + serviceGroup: '', + }, + }), + iconType: 'tableDensityExpanded', + }, + { + label: i18n.translate( + 'xpack.apm.storageExplorer.resources.documentation', + { + defaultMessage: 'Documentation', + } + ), + href: docLinks.links.apm.storageExplorer, + target: '_blank', + iconType: 'documentation', + }, + { + label: i18n.translate( + 'xpack.apm.storageExplorer.resources.sendFeedback', + { + defaultMessage: 'Send feedback', + } + ), + href: getStorageExplorerFeedbackHref(), + target: '_blank', + iconType: 'editorComment', + }, + ]; + + return ( + + +

    + {i18n.translate( + 'xpack.apm.storageExplorer.resources.accordionTitle', + { + defaultMessage: 'Tips and tricks', + } + )} +

    + + } + initialIsOpen + paddingSize="m" + > + + {cards.map(({ icon, title, description, href }) => ( + + } + title={title} + description={description} + footer={ + + {i18n.translate( + 'xpack.apm.storageExplorer.resources.learnMoreButton', + { + defaultMessage: 'Learn more', + } + )} + + } + /> + + ))} + + +

    + {i18n.translate('xpack.apm.storageExplorer.resources.title', { + defaultMessage: 'Resources', + })} +

    +
    + +
    +
    +
    +
    + ); +} diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/index.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/index.tsx index 7f2dd2d8a096f..8fe102ea72961 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState, useEffect, ReactNode } from 'react'; +import React, { useState, ReactNode } from 'react'; import { EuiInMemoryTable, EuiBasicTableColumn, @@ -14,9 +14,13 @@ import { RIGHT_ALIGNMENT, EuiToolTip, EuiIcon, + EuiProgress, + EuiPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ValuesType } from 'utility-types'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { apmServiceInventoryOptimizedSorting } from '@kbn/observability-plugin/common'; +import { AgentName } from '../../../../../typings/es_schemas/ui/fields/agent'; import { EnvironmentBadge } from '../../../shared/environment_badge'; import { asPercent } from '../../../../../common/utils/formatters'; import { ServiceLink } from '../../../shared/service_link'; @@ -27,14 +31,26 @@ import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plug import { asDynamicBytes } from '../../../../../common/utils/formatters'; import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { useApmParams } from '../../../../hooks/use_apm_params'; -import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useProgressiveFetcher } from '../../../../hooks/use_progressive_fetcher'; import { useTimeRange } from '../../../../hooks/use_time_range'; import { SizeLabel } from './size_label'; -import type { APIReturnType } from '../../../../services/rest/create_call_apm_api'; +import { joinByKey } from '../../../../../common/utils/join_by_key'; -type StorageExplorerItems = - APIReturnType<'GET /internal/apm/storage_explorer'>['serviceStatistics']; +interface StorageExplorerItem { + serviceName: string; + environments?: string[]; + size?: number; + agentName?: AgentName; + sampling?: number; +} + +enum StorageExplorerFieldName { + ServiceName = 'serviceName', + Environments = 'environments', + Sampling = 'sampling', + Size = 'size', +} export function ServicesTable() { const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState< @@ -76,7 +92,29 @@ export function ServicesTable() { setItemIdToExpandedRowMap(expandedRowMapValues); }; - const { data, status } = useProgressiveFetcher( + const useOptimizedSorting = + useKibana().services.uiSettings?.get( + apmServiceInventoryOptimizedSorting + ) || false; + + const sortedAndFilteredServicesFetch = useFetcher( + (callApmApi) => { + if (useOptimizedSorting) { + return callApmApi('GET /internal/apm/storage_explorer/get_services', { + params: { + query: { + environment, + kuery, + indexLifecyclePhase, + }, + }, + }); + } + }, + [environment, kuery, indexLifecyclePhase, useOptimizedSorting] + ); + + const serviceStatisticsFetch = useProgressiveFetcher( (callApmApi) => { return callApmApi('GET /internal/apm/storage_explorer', { params: { @@ -93,165 +131,193 @@ export function ServicesTable() { [indexLifecyclePhase, start, end, environment, kuery] ); - useEffect(() => { - // Closes any open rows when fetching new items - setItemIdToExpandedRowMap({}); - }, [status]); + const serviceStatisticsItems = + serviceStatisticsFetch.data?.serviceStatistics ?? []; + const preloadedServices = sortedAndFilteredServicesFetch.data?.services || []; + + const initialSortField = useOptimizedSorting + ? StorageExplorerFieldName.ServiceName + : StorageExplorerFieldName.Size; - const loading = - status === FETCH_STATUS.NOT_INITIATED || status === FETCH_STATUS.LOADING; + const initialSortDirection = + initialSortField === StorageExplorerFieldName.ServiceName ? 'asc' : 'desc'; - const columns: Array>> = + const loading = serviceStatisticsFetch.status === FETCH_STATUS.LOADING; + + const items = joinByKey( [ - { - field: 'serviceName', - name: i18n.translate( - 'xpack.apm.storageExplorer.table.serviceColumnName', - { - defaultMessage: 'Service', - } - ), - sortable: true, - render: (_, { serviceName, agentName }) => { - const serviceLinkQuery = { - comparisonEnabled, - environment, - kuery, - rangeFrom, - rangeTo, - serviceGroup: '', - }; + ...(initialSortField === StorageExplorerFieldName.ServiceName + ? preloadedServices + : []), + ...serviceStatisticsItems, + ], + 'serviceName' + ); - return ( - - } - /> - ); - }, - }, - { - field: 'environment', - name: i18n.translate( - 'xpack.apm.storageExplorer.table.environmentColumnName', - { - defaultMessage: 'Environment', - } - ), - render: (_, { environments }) => ( - - ), - sortable: true, - }, + const columns: Array> = [ + { + field: 'serviceName', + name: i18n.translate( + 'xpack.apm.storageExplorer.table.serviceColumnName', + { + defaultMessage: 'Service', + } + ), + sortable: true, + render: (_, { serviceName, agentName }) => { + const serviceLinkQuery = { + comparisonEnabled, + environment, + kuery, + rangeFrom, + rangeTo, + serviceGroup: '', + }; - { - field: 'sampling', - name: ( - - <> - {i18n.translate( - 'xpack.apm.storageExplorer.table.samplingColumnName', - { - defaultMessage: 'Sample rate', - } - )}{' '} - - - - ), - render: (value: string) => asPercent(parseFloat(value), 1), - sortable: true, + } + /> + ); }, - { - field: 'size', - name: , - render: (_, { size }) => asDynamicBytes(size) || NOT_AVAILABLE_LABEL, - sortable: true, - }, - { - align: RIGHT_ALIGNMENT, - width: '40px', - isExpander: true, - name: ( - - - {i18n.translate('xpack.apm.storageExplorer.table.expandRow', { - defaultMessage: 'Expand row', - })} - - - ), - render: ({ serviceName }: { serviceName: string }) => { - return ( - toggleRowDetails(serviceName)} - aria-label={ - itemIdToExpandedRowMap[serviceName] - ? i18n.translate('xpack.apm.storageExplorer.table.collapse', { - defaultMessage: 'Collapse', - }) - : i18n.translate('xpack.apm.storageExplorer.table.expand', { - defaultMessage: 'Expand', - }) - } - iconType={ - itemIdToExpandedRowMap[serviceName] ? 'arrowUp' : 'arrowDown' + }, + { + field: 'environment', + name: i18n.translate( + 'xpack.apm.storageExplorer.table.environmentColumnName', + { + defaultMessage: 'Environment', + } + ), + render: (_, { environments }) => ( + + ), + sortable: true, + }, + + { + field: 'sampling', + name: ( + + <> + {i18n.translate( + 'xpack.apm.storageExplorer.table.samplingColumnName', + { + defaultMessage: 'Sample rate', } + )}{' '} + - ); - }, + + + ), + render: (value: string) => asPercent(parseFloat(value), 1), + sortable: true, + }, + { + field: 'size', + name: , + render: (_, { size }) => asDynamicBytes(size) || NOT_AVAILABLE_LABEL, + sortable: true, + }, + { + align: RIGHT_ALIGNMENT, + width: '40px', + isExpander: true, + name: ( + + + {i18n.translate('xpack.apm.storageExplorer.table.expandRow', { + defaultMessage: 'Expand row', + })} + + + ), + render: ({ serviceName }: { serviceName: string }) => { + return ( + toggleRowDetails(serviceName)} + aria-label={ + itemIdToExpandedRowMap[serviceName] + ? i18n.translate('xpack.apm.storageExplorer.table.collapse', { + defaultMessage: 'Collapse', + }) + : i18n.translate('xpack.apm.storageExplorer.table.expand', { + defaultMessage: 'Expand', + }) + } + iconType={ + itemIdToExpandedRowMap[serviceName] ? 'arrowUp' : 'arrowDown' + } + /> + ); }, - ]; + }, + ]; return ( - + + {loading && } + + ); } diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/index_stats_per_service.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/index_stats_per_service.tsx new file mode 100644 index 0000000000000..04505e85ef806 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/index_stats_per_service.tsx @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { + EuiInMemoryTable, + EuiBasicTableColumn, + EuiPanel, + EuiTitle, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ValuesType } from 'utility-types'; +import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; +import { + asDynamicBytes, + asInteger, +} from '../../../../../common/utils/formatters'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import type { APIReturnType } from '../../../../services/rest/create_call_apm_api'; +import { SizeLabel } from './size_label'; + +type StorageExplorerIndicesStats = + APIReturnType<'GET /internal/apm/services/{serviceName}/storage_details'>['indicesStats']; + +interface Props { + indicesStats: StorageExplorerIndicesStats; + status: FETCH_STATUS; +} + +export function IndexStatsPerService({ indicesStats, status }: Props) { + const columns: Array< + EuiBasicTableColumn> + > = [ + { + field: 'indexName', + name: i18n.translate('xpack.apm.storageExplorer.indicesStats.indexName', { + defaultMessage: 'Name', + }), + sortable: true, + }, + { + field: 'primary', + name: i18n.translate('xpack.apm.storageExplorer.indicesStats.primaries', { + defaultMessage: 'Primaries', + }), + render: (_, { primary }) => primary ?? NOT_AVAILABLE_LABEL, + sortable: true, + }, + { + field: 'replica', + name: i18n.translate('xpack.apm.storageExplorer.indicesStats.replicas', { + defaultMessage: 'Replicas', + }), + render: (_, { replica }) => replica ?? NOT_AVAILABLE_LABEL, + sortable: true, + }, + { + field: 'numberOfDocs', + name: i18n.translate( + 'xpack.apm.storageExplorer.indicesStats.numberOfDocs', + { + defaultMessage: 'Docs count', + } + ), + render: (_, { numberOfDocs }) => asInteger(numberOfDocs), + sortable: true, + }, + { + field: 'size', + name: , + render: (_, { size }) => asDynamicBytes(size) ?? NOT_AVAILABLE_LABEL, + sortable: true, + }, + { + field: 'dataStream', + name: i18n.translate( + 'xpack.apm.storageExplorer.indicesStats.dataStream', + { + defaultMessage: 'Data stream', + } + ), + render: (_, { dataStream }) => dataStream ?? NOT_AVAILABLE_LABEL, + sortable: true, + }, + { + field: 'lifecyclePhase', + name: i18n.translate( + 'xpack.apm.storageExplorer.indicesStats.lifecyclePhase', + { + defaultMessage: 'Lifecycle phase', + } + ), + render: (_, { lifecyclePhase }) => lifecyclePhase ?? NOT_AVAILABLE_LABEL, + sortable: true, + }, + ]; + + const loading = + status === FETCH_STATUS.NOT_INITIATED || status === FETCH_STATUS.LOADING; + + return ( + <> + +
    + {i18n.translate('xpack.apm.storageExplorer.indicesStats.title', { + defaultMessage: 'Indices breakdown', + })} +
    +
    + + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/storage_details_per_service.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/storage_details_per_service.tsx index 0a101d3357684..4005c01f1b8f5 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/storage_details_per_service.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/services_table/storage_details_per_service.tsx @@ -41,6 +41,7 @@ import { asDynamicBytes } from '../../../../../common/utils/formatters'; import { getComparisonEnabled } from '../../../shared/time_comparison/get_comparison_enabled'; import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { SizeLabel } from './size_label'; +import { IndexStatsPerService } from './index_stats_per_service'; interface Props { serviceName: string; @@ -155,7 +156,7 @@ export function StorageDetailsPerService({ return ( <> - + @@ -265,6 +266,12 @@ export function StorageDetailsPerService({ + + + ); diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/storage_chart.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/storage_chart.tsx index 4aeb59fe58f9c..5ca58ebc7559e 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/storage_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/storage_chart.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { euiPaletteColorBlind, EuiPanel } from '@elastic/eui'; +import { euiPaletteColorBlind } from '@elastic/eui'; import { AreaSeries, Axis, @@ -83,56 +83,54 @@ export function StorageChart() { const isEmpty = isTimeseriesEmpty(storageTimeSeries); return ( - - - - + + + + + {storageTimeSeries.map((serie) => ( + - - - {storageTimeSeries.map((serie) => ( - - ))} - - - + ))} + + ); } diff --git a/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx b/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx index c5ffb336ca549..bb94e5c4507ca 100644 --- a/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx +++ b/x-pack/plugins/apm/public/components/app/storage_explorer/summary_stats.tsx @@ -14,30 +14,28 @@ import { EuiText, useEuiFontSize, EuiLink, - EuiLoadingSpinner, + EuiToolTip, + EuiIcon, + EuiProgress, + EuiLoadingContent, + EuiSpacer, } from '@elastic/eui'; import { useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; +import { isEmpty } from 'lodash'; import { useProgressiveFetcher } from '../../../hooks/use_progressive_fetcher'; import { useTimeRange } from '../../../hooks/use_time_range'; import { useApmParams } from '../../../hooks/use_apm_params'; -import { asDynamicBytes } from '../../../../common/utils/formatters'; +import { asDynamicBytes, asPercent } from '../../../../common/utils/formatters'; import { useApmRouter } from '../../../hooks/use_apm_router'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { asTransactionRate } from '../../../../common/utils/formatters'; - -const INITIAL_DATA = { - estimatedSize: 0, - dailyDataGeneration: 0, - tracesPerMinute: 0, - numberOfServices: 0, -}; +import { getIndexManagementHref } from './get_storage_explorer_links'; export function SummaryStats() { const router = useApmRouter(); const { core } = useApmPluginContext(); - const { euiTheme } = useEuiTheme(); const { query: { @@ -63,7 +61,7 @@ export function SummaryStats() { }, }); - const { data = INITIAL_DATA, status } = useProgressiveFetcher( + const { data, status } = useProgressiveFetcher( (callApmApi) => { return callApmApi('GET /internal/apm/storage_explorer_summary_stats', { params: { @@ -80,92 +78,136 @@ export function SummaryStats() { [indexLifecyclePhase, environment, kuery, start, end] ); - const loading = status === FETCH_STATUS.LOADING; + const loading = + status === FETCH_STATUS.LOADING || status === FETCH_STATUS.NOT_INITIATED; + + const hasData = !isEmpty(data); return ( - - {loading && ( - - - - )} - {!loading && ( - - - - - - + {loading && } + + + + + + + + + + + + + + + + + {i18n.translate( + 'xpack.apm.storageExplorer.summary.serviceInventoryLink', { - defaultMessage: 'Traces per minute', + defaultMessage: 'Go to Service Inventory', } )} - value={asTransactionRate(data?.tracesPerMinute)} - color={euiTheme.colors.accent} - /> - + + + + {i18n.translate( + 'xpack.apm.storageExplorer.summary.indexManagementLink', { - defaultMessage: 'Number of services', + defaultMessage: 'Go to Index Management', } )} - value={data?.numberOfServices.toString()} - color={euiTheme.colors.success} - /> - - - - - - - - {i18n.translate( - 'xpack.apm.storageExplorer.summary.serviceInventoryLink', - { - defaultMessage: 'Go to Service Inventory', - } - )} - - - - - {i18n.translate( - 'xpack.apm.storageExplorer.summary.indexManagementLink', - { - defaultMessage: 'Go to Index Management', - } - )} - - - - - - )} + + + + + ); } @@ -173,29 +215,55 @@ export function SummaryStats() { function SummaryMetric({ label, value, - color, + tooltipContent, + loading, + hasData, }: { label: string; value: string; - color: string; + tooltipContent?: string; + loading: boolean; + hasData: boolean; }) { - const xxlFontSize = useEuiFontSize('xxl', { measurement: 'px' }); + const xlFontSize = useEuiFontSize('xl', { measurement: 'px' }); const { euiTheme } = useEuiTheme(); return ( - - {label} - - - {value} - + {tooltipContent ? ( + + + {label}{' '} + + + + ) : ( + + {label} + + )} + {loading && !hasData && ( + <> + + + + )} + {hasData && ( + + {value} + + )} ); } diff --git a/x-pack/plugins/apm/public/components/routing/home/storage_explorer.tsx b/x-pack/plugins/apm/public/components/routing/home/storage_explorer.tsx index c4bc86f766811..167d4f88ca8b5 100644 --- a/x-pack/plugins/apm/public/components/routing/home/storage_explorer.tsx +++ b/x-pack/plugins/apm/public/components/routing/home/storage_explorer.tsx @@ -6,18 +6,18 @@ */ import React from 'react'; -import { EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import * as t from 'io-ts'; import { EuiLink } from '@elastic/eui'; import { StorageExplorer } from '../../app/storage_explorer'; -import { BetaBadge } from '../../shared/beta_badge'; import { ApmMainTemplate } from '../templates/apm_main_template'; import { Breadcrumb } from '../../app/breadcrumb'; import { indexLifecyclePhaseRt, IndexLifecyclePhaseSelectOption, } from '../../../../common/storage_explorer_types'; +import { getStorageExplorerFeedbackHref } from '../../app/storage_explorer/get_storage_explorer_links'; export const storageExplorer = { '/storage-explorer': { @@ -32,30 +32,16 @@ export const storageExplorer = { pageHeader={{ alignItems: 'center', pageTitle: ( - - - -

    - {i18n.translate('xpack.apm.views.storageExplorer.title', { - defaultMessage: 'Storage explorer', - })} -

    -
    -
    - - - -
    + +

    + {i18n.translate('xpack.apm.views.storageExplorer.title', { + defaultMessage: 'Storage explorer', + })} +

    +
    ), rightSideItems: [ - + {i18n.translate( 'xpack.apm.views.storageExplorer.giveFeedback', { diff --git a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx index ad3def1903517..58ff1bbfdb6ca 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/index.tsx @@ -50,7 +50,7 @@ export function SparkPlot({ valueLabel: React.ReactNode; compact?: boolean; comparisonSeries?: Coordinate[]; - comparisonSeriesColor: string; + comparisonSeriesColor?: string; }) { return ( { }); it('enables auto-refresh when refreshPaused is false', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const { wrapper } = mountDatePicker({ rangeFrom: 'now-15m', rangeTo: 'now', @@ -139,7 +139,7 @@ describe('DatePicker', () => { }); it('disables auto-refresh when refreshPaused is true', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); mountDatePicker({ rangeFrom: 'now-15m', rangeTo: 'now', diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx index 4b19ea5dddbda..27f29b7dff12e 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx @@ -23,7 +23,7 @@ describe('when simulating race condition', () => { let renderSpy: jest.Mock; beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); renderSpy = jest.fn(); requestCallOrder = []; diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx index ed4d1f0791992..a25fcb7245451 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx @@ -36,7 +36,7 @@ describe('useFetcher', () => { >; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); async function fn() { await delay(500); return 'response from hook'; @@ -86,7 +86,7 @@ describe('useFetcher', () => { >; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); async function fn(): Promise { await delay(500); throw new Error('Something went wrong'); @@ -129,7 +129,7 @@ describe('useFetcher', () => { describe('when a hook already has data', () => { it('should show "first response" while loading "second response"', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const hook = renderHook( /* eslint-disable-next-line react-hooks/exhaustive-deps */ diff --git a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts index 8c55178d7bbed..624c3c626fe97 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts @@ -32,7 +32,7 @@ export function useServiceMetricChartsFetcher({ } = useApmParams('/services/{serviceName}'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const { agentName, serviceName, runtimeName } = useApmServiceContext(); + const { agentName, serviceName } = useApmServiceContext(); const { data = INITIAL_DATA, @@ -53,23 +53,13 @@ export function useServiceMetricChartsFetcher({ start, end, agentName, - serviceRuntimeName: runtimeName, }, }, } ); } }, - [ - environment, - kuery, - serviceName, - start, - end, - agentName, - serviceNodeName, - runtimeName, - ] + [environment, kuery, serviceName, start, end, agentName, serviceNodeName] ); return { diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/agent_config_instructions.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/agent_config_instructions.tsx index c7244002e59f5..0e4ad1f3f44a0 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/agent_config_instructions.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/agent_config_instructions.tsx @@ -25,7 +25,7 @@ export function AgentConfigInstructions({ }) { const defaultValues = { apmServiceName: 'my-service-name', - apmEnvironment: 'production', + apmEnvironment: 'my-environment', }; if (variantId === 'openTelemetry') { @@ -60,7 +60,11 @@ export function AgentConfigInstructions({ /> - + {commands} diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/django.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/django.ts index 4379f15c59cde..15279a71a6573 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/commands/django.ts +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/django.ts @@ -9,8 +9,8 @@ import { i18n } from '@kbn/i18n'; export const djangoVariables = { apmServiceName: 'SERVICE_NAME', - apmServerUrl: 'SERVER_URL', secretToken: 'SECRET_TOKEN', + apmServerUrl: 'SERVER_URL', apmEnvironment: 'ENVIRONMENT', }; @@ -21,50 +21,50 @@ export const django = `# ${i18n.translate( } )} INSTALLED_APPS = ( -'elasticapm.contrib.django', -# ... + 'elasticapm.contrib.django', + # ... ) ELASTIC_APM = { -# ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.setRequiredServiceNameComment', - { - defaultMessage: 'Set the required service name. Allowed characters:', - } -)} -# ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.allowedCharactersComment', - { - defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', - } -)} -#'${djangoVariables.apmServiceName}': '{{{apmServiceName}}}', + # ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.setRequiredServiceNameComment', + { + defaultMessage: 'Set the required service name. Allowed characters:', + } + )} + # ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.allowedCharactersComment', + { + defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', + } + )} + #'${djangoVariables.apmServiceName}': '{{{apmServiceName}}}', -# ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.useIfApmServerRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a secret token', - } -)} -'${djangoVariables.secretToken}': '{{{secretToken}}}', + # ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.useIfApmServerRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a secret token', + } + )} + '${djangoVariables.secretToken}': '{{{secretToken}}}', -# ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment', - { - defaultMessage: - 'Set the custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } -)} -'${djangoVariables.apmServerUrl}': '{{{apmServerUrl}}}', + # ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set the custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } + )} + '${djangoVariables.apmServerUrl}': '{{{apmServerUrl}}}', -# ${i18n.translate( - 'xpack.apm.tutorial.djangoClient.configure.commands.setServiceEnvironmentComment', - { - defaultMessage: 'Set the service environment', - } -)} -'${djangoVariables.apmEnvironment}': '{{{apmEnvironment}}}', + # ${i18n.translate( + 'xpack.apm.tutorial.djangoClient.configure.commands.setServiceEnvironmentComment', + { + defaultMessage: 'Set the service environment', + } + )} + '${djangoVariables.apmEnvironment}': '{{{apmEnvironment}}}', } # ${i18n.translate( @@ -74,6 +74,6 @@ ELASTIC_APM = { } )} MIDDLEWARE = ( -'elasticapm.contrib.django.middleware.TracingMiddleware', -#... + 'elasticapm.contrib.django.middleware.TracingMiddleware', + #... )`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/flask.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/flask.ts index 11423c4e059db..a6289c0a88c1b 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/commands/flask.ts +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/flask.ts @@ -33,45 +33,45 @@ apm = ElasticAPM(app) )} from elasticapm.contrib.flask import ElasticAPM app.config['ELASTIC_APM'] = { -# ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.setRequiredServiceNameComment', - { - defaultMessage: 'Set the required service name. Allowed characters:', - } -)} -# ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.allowedCharactersComment', - { - defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', - } -)} -#'${flaskVariables.apmServiceName}': '{{{apmServiceName}}}', + # ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.setRequiredServiceNameComment', + { + defaultMessage: 'Set the required service name. Allowed characters:', + } + )} + # ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.allowedCharactersComment', + { + defaultMessage: 'a-z, A-Z, 0-9, -, _, and space', + } + )} + #'${flaskVariables.apmServiceName}': '{{{apmServiceName}}}', -# ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.useIfApmServerRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a secret token', - } -)} -'${flaskVariables.secretToken}': '{{{secretToken}}}', + # ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.useIfApmServerRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a secret token', + } + )} + '${flaskVariables.secretToken}': '{{{secretToken}}}', -# ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.setCustomApmServerUrlComment', - { - defaultMessage: - 'Set the custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } -)} -'${flaskVariables.apmServerUrl}': '{{{apmServerUrl}}}', + # ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set the custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } + )} + '${flaskVariables.apmServerUrl}': '{{{apmServerUrl}}}', -# ${i18n.translate( - 'xpack.apm.tutorial.flaskClient.configure.commands.setServiceEnvironmentComment', - { - defaultMessage: 'Set the service environment', - } -)} -'${flaskVariables.apmEnvironment}': '{{{apmEnvironment}}}', + # ${i18n.translate( + 'xpack.apm.tutorial.flaskClient.configure.commands.setServiceEnvironmentComment', + { + defaultMessage: 'Set the service environment', + } + )} + '${flaskVariables.apmEnvironment}': '{{{apmEnvironment}}}', } apm = ElasticAPM(app)`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/get_apm_agent_commands.test.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/get_apm_agent_commands.test.ts index efbcfae955a55..d01d3acc5d519 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/commands/get_apm_agent_commands.test.ts +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/get_apm_agent_commands.test.ts @@ -10,7 +10,7 @@ import { getApmAgentCommands } from './get_apm_agent_commands'; describe('getCommands', () => { const defaultValues = { apmServiceName: 'my-service-name', - apmEnvironment: 'production', + apmEnvironment: 'my-environment', }; describe('unknown agent', () => { it('renders empty command', () => { @@ -37,7 +37,7 @@ describe('getCommands', () => { -Delastic.apm.service_name=my-service-name \\\\ -Delastic.apm.secret_token= \\\\ -Delastic.apm.server_url= \\\\ - -Delastic.apm.environment=production \\\\ + -Delastic.apm.environment=my-environment \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-service-name.jar" `); @@ -57,7 +57,7 @@ describe('getCommands', () => { -Delastic.apm.service_name=my-service-name \\\\ -Delastic.apm.secret_token=foobar \\\\ -Delastic.apm.server_url=localhost:8220 \\\\ - -Delastic.apm.environment=production \\\\ + -Delastic.apm.environment=my-environment \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-service-name.jar" `); @@ -85,7 +85,7 @@ describe('getCommands', () => { serviceVersion: '', // Set the service environment - environment: 'production' + environment: 'my-environment' })" `); }); @@ -113,7 +113,7 @@ describe('getCommands', () => { serviceVersion: '', // Set the service environment - environment: 'production' + environment: 'my-environment' })" `); }); @@ -130,18 +130,18 @@ describe('getCommands', () => { "// Add this to the VERY top of the first file loaded in your app var apm = require('elastic-apm-node').start({ - // Override the service name from package.json - // Allowed characters: a-z, A-Z, 0-9, -, _, and space - serviceName: 'my-service-name', + // Override the service name from package.json + // Allowed characters: a-z, A-Z, 0-9, -, _, and space + serviceName: 'my-service-name', - // Use if APM Server requires a secret token - secretToken: '', + // Use if APM Server requires a secret token + secretToken: '', - // Set the custom APM Server URL (default: http://localhost:8200) - serverUrl: '', + // Set the custom APM Server URL (default: http://localhost:8200) + serverUrl: '', - // Set the service environment - environment: 'production' + // Set the service environment + environment: 'my-environment' })" `); }); @@ -159,18 +159,18 @@ describe('getCommands', () => { "// Add this to the VERY top of the first file loaded in your app var apm = require('elastic-apm-node').start({ - // Override the service name from package.json - // Allowed characters: a-z, A-Z, 0-9, -, _, and space - serviceName: 'my-service-name', + // Override the service name from package.json + // Allowed characters: a-z, A-Z, 0-9, -, _, and space + serviceName: 'my-service-name', - // Use if APM Server requires a secret token - secretToken: 'foobar', + // Use if APM Server requires a secret token + secretToken: 'foobar', - // Set the custom APM Server URL (default: http://localhost:8200) - serverUrl: 'localhost:8220', + // Set the custom APM Server URL (default: http://localhost:8200) + serverUrl: 'localhost:8220', - // Set the service environment - environment: 'production' + // Set the service environment + environment: 'my-environment' })" `); }); @@ -186,29 +186,29 @@ describe('getCommands', () => { expect(commands).toMatchInlineSnapshot(` "# Add the agent to the installed apps INSTALLED_APPS = ( - 'elasticapm.contrib.django', - # ... + 'elasticapm.contrib.django', + # ... ) ELASTIC_APM = { - # Set the required service name. Allowed characters: - # a-z, A-Z, 0-9, -, _, and space - #'SERVICE_NAME': 'my-service-name', + # Set the required service name. Allowed characters: + # a-z, A-Z, 0-9, -, _, and space + #'SERVICE_NAME': 'my-service-name', - # Use if APM Server requires a secret token - 'SECRET_TOKEN': '', + # Use if APM Server requires a secret token + 'SECRET_TOKEN': '', - # Set the custom APM Server URL (default: http://localhost:8200) - 'SERVER_URL': '', + # Set the custom APM Server URL (default: http://localhost:8200) + 'SERVER_URL': '', - # Set the service environment - 'ENVIRONMENT': 'production', + # Set the service environment + 'ENVIRONMENT': 'my-environment', } # To send performance metrics, add our tracing middleware: MIDDLEWARE = ( - 'elasticapm.contrib.django.middleware.TracingMiddleware', - #... + 'elasticapm.contrib.django.middleware.TracingMiddleware', + #... )" `); }); @@ -225,29 +225,29 @@ describe('getCommands', () => { expect(commands).toMatchInlineSnapshot(` "# Add the agent to the installed apps INSTALLED_APPS = ( - 'elasticapm.contrib.django', - # ... + 'elasticapm.contrib.django', + # ... ) ELASTIC_APM = { - # Set the required service name. Allowed characters: - # a-z, A-Z, 0-9, -, _, and space - #'SERVICE_NAME': 'my-service-name', + # Set the required service name. Allowed characters: + # a-z, A-Z, 0-9, -, _, and space + #'SERVICE_NAME': 'my-service-name', - # Use if APM Server requires a secret token - 'SECRET_TOKEN': 'foobar', + # Use if APM Server requires a secret token + 'SECRET_TOKEN': 'foobar', - # Set the custom APM Server URL (default: http://localhost:8200) - 'SERVER_URL': 'localhost:8220', + # Set the custom APM Server URL (default: http://localhost:8200) + 'SERVER_URL': 'localhost:8220', - # Set the service environment - 'ENVIRONMENT': 'production', + # Set the service environment + 'ENVIRONMENT': 'my-environment', } # To send performance metrics, add our tracing middleware: MIDDLEWARE = ( - 'elasticapm.contrib.django.middleware.TracingMiddleware', - #... + 'elasticapm.contrib.django.middleware.TracingMiddleware', + #... )" `); }); @@ -269,18 +269,18 @@ describe('getCommands', () => { # or configure to use ELASTIC_APM in your application's settings from elasticapm.contrib.flask import ElasticAPM app.config['ELASTIC_APM'] = { - # Set the required service name. Allowed characters: - # a-z, A-Z, 0-9, -, _, and space - #'SERVICE_NAME': 'my-service-name', + # Set the required service name. Allowed characters: + # a-z, A-Z, 0-9, -, _, and space + #'SERVICE_NAME': 'my-service-name', - # Use if APM Server requires a secret token - 'SECRET_TOKEN': '', + # Use if APM Server requires a secret token + 'SECRET_TOKEN': '', - # Set the custom APM Server URL (default: http://localhost:8200) - 'SERVER_URL': '', + # Set the custom APM Server URL (default: http://localhost:8200) + 'SERVER_URL': '', - # Set the service environment - 'ENVIRONMENT': 'production', + # Set the service environment + 'ENVIRONMENT': 'my-environment', } apm = ElasticAPM(app)" @@ -305,18 +305,18 @@ describe('getCommands', () => { # or configure to use ELASTIC_APM in your application's settings from elasticapm.contrib.flask import ElasticAPM app.config['ELASTIC_APM'] = { - # Set the required service name. Allowed characters: - # a-z, A-Z, 0-9, -, _, and space - #'SERVICE_NAME': 'my-service-name', + # Set the required service name. Allowed characters: + # a-z, A-Z, 0-9, -, _, and space + #'SERVICE_NAME': 'my-service-name', - # Use if APM Server requires a secret token - 'SECRET_TOKEN': 'foobar', + # Use if APM Server requires a secret token + 'SECRET_TOKEN': 'foobar', - # Set the custom APM Server URL (default: http://localhost:8200) - 'SERVER_URL': 'localhost:8220', + # Set the custom APM Server URL (default: http://localhost:8200) + 'SERVER_URL': 'localhost:8220', - # Set the service environment - 'ENVIRONMENT': 'production', + # Set the service environment + 'ENVIRONMENT': 'my-environment', } apm = ElasticAPM(app)" @@ -345,7 +345,7 @@ describe('getCommands', () => { server_url: '' # Set the service environment - environment: 'production'" + environment: 'my-environment'" `); }); it('renders with secret token and url', () => { @@ -372,7 +372,7 @@ describe('getCommands', () => { server_url: 'localhost:8220' # Set the service environment - environment: 'production'" + environment: 'my-environment'" `); }); }); @@ -398,7 +398,7 @@ describe('getCommands', () => { server_url: '', # Set the service environment - environment: 'production'" + environment: 'my-environment'" `); }); it('renders with secret token and url', () => { @@ -425,7 +425,7 @@ describe('getCommands', () => { server_url: 'localhost:8220', # Set the service environment - environment: 'production'" + environment: 'my-environment'" `); }); }); @@ -451,7 +451,7 @@ describe('getCommands', () => { export ELASTIC_APM_SERVER_URL= # Set the service environment - export ELASTIC_APM_ENVIRONMENT=production + export ELASTIC_APM_ENVIRONMENT=my-environment " `); }); @@ -479,7 +479,7 @@ describe('getCommands', () => { export ELASTIC_APM_SERVER_URL=localhost:8220 # Set the service environment - export ELASTIC_APM_ENVIRONMENT=production + export ELASTIC_APM_ENVIRONMENT=my-environment " `); }); @@ -498,7 +498,7 @@ describe('getCommands', () => { \\"ServiceName\\": \\"my-service-name\\", //allowed characters: a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application \\"SecretToken\\": \\"\\", \\"ServerUrl\\": \\"\\", //Set custom APM Server URL (default: http://localhost:8200) - \\"Environment\\": \\"production\\", // Set the service environment + \\"Environment\\": \\"my-environment\\", // Set the service environment } }" `); @@ -519,7 +519,7 @@ describe('getCommands', () => { \\"ServiceName\\": \\"my-service-name\\", //allowed characters: a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application \\"SecretToken\\": \\"foobar\\", \\"ServerUrl\\": \\"localhost:8220\\", //Set custom APM Server URL (default: http://localhost:8200) - \\"Environment\\": \\"production\\", // Set the service environment + \\"Environment\\": \\"my-environment\\", // Set the service environment } }" `); @@ -537,7 +537,7 @@ describe('getCommands', () => { "elastic_apm.service_name=\\"my-service-name\\" elastic_apm.secret_token=\\"\\" elastic_apm.server_url=\\"\\" - elastic_apm.environment=\\"production\\" + elastic_apm.environment=\\"my-environment\\" " `); }); @@ -555,7 +555,7 @@ describe('getCommands', () => { "elastic_apm.service_name=\\"my-service-name\\" elastic_apm.secret_token=\\"foobar\\" elastic_apm.server_url=\\"localhost:8220\\" - elastic_apm.environment=\\"production\\" + elastic_apm.environment=\\"my-environment\\" " `); }); diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/java.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/java.ts index 2d056b3a242f3..4edcb6aab2170 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/commands/java.ts +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/java.ts @@ -16,6 +16,6 @@ export const java = `java -javaagent:/path/to/elastic-apm-agent-.jar \\ -${javaVariables.apmServiceName}={{{apmServiceName}}} \\ -${javaVariables.secretToken}={{{secretToken}}} \\ -${javaVariables.apmServerUrl}={{{apmServerUrl}}} \\ --${javaVariables.apmEnvironment}=production \\ +-${javaVariables.apmEnvironment}={{{apmEnvironment}}} \\ -Delastic.apm.application_packages=org.example \\ -jar {{{apmServiceName}}}.jar`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/commands/node.ts b/x-pack/plugins/apm/public/tutorial/config_agent/commands/node.ts index 8b8ba07a7cb9c..2510599cff94e 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/commands/node.ts +++ b/x-pack/plugins/apm/public/tutorial/config_agent/commands/node.ts @@ -22,43 +22,43 @@ export const node = `// ${i18n.translate( )} var apm = require('elastic-apm-node').start({ -// ${i18n.translate( - 'xpack.apm.tutorial.nodeClient.configure.commands.setRequiredServiceNameComment', - { - defaultMessage: 'Override the service name from package.json', - } -)} -// ${i18n.translate( - 'xpack.apm.tutorial.nodeClient.configure.commands.allowedCharactersComment', - { - defaultMessage: 'Allowed characters: a-z, A-Z, 0-9, -, _, and space', - } -)} -${nodeVariables.apmServiceName}: '{{{apmServiceName}}}', + // ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.setRequiredServiceNameComment', + { + defaultMessage: 'Override the service name from package.json', + } + )} + // ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.allowedCharactersComment', + { + defaultMessage: 'Allowed characters: a-z, A-Z, 0-9, -, _, and space', + } + )} + ${nodeVariables.apmServiceName}: '{{{apmServiceName}}}', -// ${i18n.translate( - 'xpack.apm.tutorial.nodeClient.configure.commands.useIfApmRequiresTokenComment', - { - defaultMessage: 'Use if APM Server requires a secret token', - } -)} -${nodeVariables.secretToken}: '{{{secretToken}}}', + // ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.useIfApmRequiresTokenComment', + { + defaultMessage: 'Use if APM Server requires a secret token', + } + )} + ${nodeVariables.secretToken}: '{{{secretToken}}}', -// ${i18n.translate( - 'xpack.apm.tutorial.nodeClient.configure.commands.setCustomApmServerUrlComment', - { - defaultMessage: - 'Set the custom APM Server URL (default: {defaultApmServerUrl})', - values: { defaultApmServerUrl: 'http://localhost:8200' }, - } -)} -${nodeVariables.apmServerUrl}: '{{{apmServerUrl}}}', + // ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.setCustomApmServerUrlComment', + { + defaultMessage: + 'Set the custom APM Server URL (default: {defaultApmServerUrl})', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } + )} + ${nodeVariables.apmServerUrl}: '{{{apmServerUrl}}}', -// ${i18n.translate( - 'xpack.apm.tutorial.nodeClient.configure.commands.setCustomServiceEnvironmentComment', - { - defaultMessage: 'Set the service environment', - } -)} -${nodeVariables.apmEnvironment}: '{{{apmEnvironment}}}' + // ${i18n.translate( + 'xpack.apm.tutorial.nodeClient.configure.commands.setCustomServiceEnvironmentComment', + { + defaultMessage: 'Set the service environment', + } + )} + ${nodeVariables.apmEnvironment}: '{{{apmEnvironment}}}' })`; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx index b1ebe783518d0..edae01e0b6043 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx @@ -90,7 +90,7 @@ describe('TutorialConfigAgent', () => { -Delastic.apm.service_name=my-service-name \\\\ -Delastic.apm.secret_token= \\\\ -Delastic.apm.server_url=http://localhost:8200 \\\\ - -Delastic.apm.environment=production \\\\ + -Delastic.apm.environment=my-environment \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-service-name.jar" `); @@ -104,7 +104,7 @@ describe('TutorialConfigAgent', () => { -Delastic.apm.service_name=my-service-name \\\\ -Delastic.apm.secret_token=foo token \\\\ -Delastic.apm.server_url=foo url \\\\ - -Delastic.apm.environment=production \\\\ + -Delastic.apm.environment=my-environment \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-service-name.jar" `); @@ -141,7 +141,7 @@ describe('TutorialConfigAgent', () => { -Delastic.apm.service_name=my-service-name \\\\ -Delastic.apm.secret_token= \\\\ -Delastic.apm.server_url=http://localhost:8200 \\\\ - -Delastic.apm.environment=production \\\\ + -Delastic.apm.environment=my-environment \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-service-name.jar" `); @@ -177,7 +177,7 @@ describe('TutorialConfigAgent', () => { -Delastic.apm.service_name=my-service-name \\\\ -Delastic.apm.secret_token= \\\\ -Delastic.apm.server_url=http://localhost:8200 \\\\ - -Delastic.apm.environment=production \\\\ + -Delastic.apm.environment=my-environment \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-service-name.jar" `); @@ -219,7 +219,7 @@ describe('TutorialConfigAgent', () => { -Delastic.apm.service_name=my-service-name \\\\ -Delastic.apm.secret_token=cloud_token \\\\ -Delastic.apm.server_url=cloud_url \\\\ - -Delastic.apm.environment=production \\\\ + -Delastic.apm.environment=my-environment \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-service-name.jar" `); @@ -258,7 +258,7 @@ describe('TutorialConfigAgent', () => { -Delastic.apm.service_name=my-service-name \\\\ -Delastic.apm.secret_token=apm_cloud_token \\\\ -Delastic.apm.server_url=apm_cloud_url \\\\ - -Delastic.apm.environment=production \\\\ + -Delastic.apm.environment=my-environment \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-service-name.jar" `); @@ -290,7 +290,7 @@ describe('TutorialConfigAgent', () => { -Delastic.apm.service_name=my-service-name \\\\ -Delastic.apm.secret_token= \\\\ -Delastic.apm.server_url=http://localhost:8200 \\\\ - -Delastic.apm.environment=production \\\\ + -Delastic.apm.environment=my-environment \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-service-name.jar" `); @@ -353,7 +353,7 @@ describe('TutorialConfigAgent', () => { -Delastic.apm.service_name=my-service-name \\\\ -Delastic.apm.secret_token= \\\\ -Delastic.apm.server_url=http://localhost:8200 \\\\ - -Delastic.apm.environment=production \\\\ + -Delastic.apm.environment=my-environment \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-service-name.jar" `); @@ -390,7 +390,7 @@ describe('TutorialConfigAgent', () => { -Delastic.apm.service_name=my-service-name \\\\ -Delastic.apm.secret_token=cloud_token \\\\ -Delastic.apm.server_url=cloud_url \\\\ - -Delastic.apm.environment=production \\\\ + -Delastic.apm.environment=my-environment \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-service-name.jar" `); diff --git a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_destination_map.ts b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_destination_map.ts index a9c7745178219..4c76699d2089b 100644 --- a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_destination_map.ts +++ b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_destination_map.ts @@ -24,10 +24,10 @@ import { SPAN_SUBTYPE, SPAN_TYPE, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../helpers/setup_request'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Node, NodeType } from '../../../../common/connections'; import { excludeRumExitSpansQuery } from '../exclude_rum_exit_spans_query'; +import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client'; type Destination = { dependencyName: string; @@ -48,21 +48,19 @@ type Destination = { // - for each span, find the transaction it creates // - if there is a transaction, match the dependency name (span.destination.service.resource) to a service export const getDestinationMap = ({ - setup, + apmEventClient, start, end, filter, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; filter: QueryDslQueryContainer[]; offset?: string; }) => { return withApmSpan('get_destination_map', async () => { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts index 7057847c34c0c..409565db5729f 100644 --- a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts +++ b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/get_stats.ts @@ -27,27 +27,25 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { EventOutcome } from '../../../../common/event_outcome'; -import { Setup } from '../../helpers/setup_request'; import { NodeType } from '../../../../common/connections'; import { excludeRumExitSpansQuery } from '../exclude_rum_exit_spans_query'; +import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client'; export const getStats = async ({ - setup, + apmEventClient, start, end, filter, numBuckets, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; filter: QueryDslQueryContainer[]; numBuckets: number; offset?: string; }) => { - const { apmEventClient } = setup; - const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/index.ts b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/index.ts index 7c9781e08db31..4ba113b698bc1 100644 --- a/x-pack/plugins/apm/server/lib/connections/get_connection_stats/index.ts +++ b/x-pack/plugins/apm/server/lib/connections/get_connection_stats/index.ts @@ -9,14 +9,14 @@ import { ValuesType } from 'utility-types'; import { merge } from 'lodash'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { joinByKey } from '../../../../common/utils/join_by_key'; -import { Setup } from '../../helpers/setup_request'; import { getStats } from './get_stats'; import { getDestinationMap } from './get_destination_map'; import { calculateThroughputWithRange } from '../../helpers/calculate_throughput'; import { withApmSpan } from '../../../utils/with_apm_span'; +import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client'; export function getConnectionStats({ - setup, + apmEventClient, start, end, numBuckets, @@ -24,7 +24,7 @@ export function getConnectionStats({ collapseBy, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; numBuckets: number; @@ -35,7 +35,7 @@ export function getConnectionStats({ return withApmSpan('get_connection_stats_and_map', async () => { const [allMetrics, destinationMap] = await Promise.all([ getStats({ - setup, + apmEventClient, start, end, filter, @@ -43,7 +43,7 @@ export function getConnectionStats({ offset, }), getDestinationMap({ - setup, + apmEventClient, start, end, filter, diff --git a/x-pack/plugins/apm/server/lib/helpers/get_apm_event_client.ts b/x-pack/plugins/apm/server/lib/helpers/get_apm_event_client.ts new file mode 100644 index 0000000000000..6ee9bb94a62b2 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/helpers/get_apm_event_client.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 { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { APMRouteHandlerResources } from '../../routes/typings'; +import { getApmIndices } from '../../routes/settings/apm_indices/get_apm_indices'; +import { APMEventClient } from './create_es_client/create_apm_event_client'; +import { withApmSpan } from '../../utils/with_apm_span'; + +export async function getApmEventClient({ + context, + params, + config, + request, +}: APMRouteHandlerResources): Promise { + return withApmSpan('get_apm_event_client', async () => { + const coreContext = await context.core; + const [indices, includeFrozen] = await Promise.all([ + getApmIndices({ + savedObjectsClient: coreContext.savedObjects.client, + config, + }), + withApmSpan('get_ui_settings', () => + coreContext.uiSettings.client.get( + UI_SETTINGS.SEARCH_INCLUDE_FROZEN + ) + ), + ]); + + return new APMEventClient({ + esClient: coreContext.elasticsearch.client.asCurrentUser, + debug: params.query._inspect, + request, + indices, + options: { includeFrozen }, + }); + }); +} diff --git a/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts b/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts index 48936b644c2af..1f68eaef98aae 100644 --- a/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/service_metrics/index.ts @@ -54,7 +54,7 @@ export async function getHasServicesMetrics({ }, body: { track_total_hits: 1, - size: 1, + size: 0, query: { bool: { filter: [ diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts index faaeb5db91405..ef8dcbbe63ee2 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -9,7 +9,6 @@ import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import { setupRequest } from './setup_request'; import { APMConfig } from '../..'; import { APMRouteHandlerResources } from '../../routes/typings'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { getApmIndices } from '../../routes/settings/apm_indices/get_apm_indices'; jest.mock('../../routes/settings/apm_indices/get_apm_indices', () => ({ @@ -97,38 +96,6 @@ function getMockResources() { describe('setupRequest', () => { describe('with default args', () => { - it('calls callWithRequest', async () => { - const mockResources = getMockResources(); - const { apmEventClient } = await setupRequest(mockResources); - await apmEventClient.search('foo', { - apm: { events: [ProcessorEvent.transaction] }, - body: { track_total_hits: 10_000, size: 10 }, - }); - - expect( - mockResources.context.core.elasticsearch.client.asCurrentUser.search - ).toHaveBeenCalledWith( - { - index: ['apm-*'], - body: { - track_total_hits: 10000, - size: 10, - query: { - bool: { - filter: [{ terms: { 'processor.event': ['transaction'] } }], - }, - }, - }, - ignore_unavailable: true, - preference: 'any', - }, - { - signal: expect.any(Object), - meta: true, - } - ); - }); - it('calls callWithInternalUser', async () => { const mockResources = getMockResources(); const { internalClient } = await setupRequest(mockResources); @@ -153,49 +120,3 @@ describe('setupRequest', () => { }); }); }); - -describe('with includeFrozen=false', () => { - it('should NOT send "ignore_throttled:true" in the request', async () => { - const mockResources = getMockResources(); - - // mock includeFrozen to return false - mockResources.context.core.uiSettings.client.get.mockResolvedValue(false); - - const { apmEventClient } = await setupRequest(mockResources); - - await apmEventClient.search('foo', { - apm: { - events: [], - }, - body: { track_total_hits: 10_000, size: 10 }, - }); - - const params = - mockResources.context.core.elasticsearch.client.asCurrentUser.search.mock - .calls[0][0]; - // @ts-expect-error missing body definition - expect(params.ignore_throttled).toBe(undefined); - }); -}); - -describe('with includeFrozen=true', () => { - it('sets `ignore_throttled=false`', async () => { - const mockResources = getMockResources(); - - // mock includeFrozen to return true - mockResources.context.core.uiSettings.client.get.mockResolvedValue(true); - - const { apmEventClient } = await setupRequest(mockResources); - - await apmEventClient.search('foo', { - apm: { events: [] }, - body: { track_total_hits: 10_000, size: 10 }, - }); - - const params = - mockResources.context.core.elasticsearch.client.asCurrentUser.search.mock - .calls[0][0]; - // @ts-expect-error missing body definition - expect(params.ignore_throttled).toBe(false); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts index dc6e379367408..457c002c67b97 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -14,7 +14,6 @@ import { ApmIndicesConfig, getApmIndices, } from '../../routes/settings/apm_indices/get_apm_indices'; -import { APMEventClient } from './create_es_client/create_apm_event_client'; import { APMInternalClient, createInternalESClient, @@ -25,7 +24,6 @@ import { withApmSpan } from '../../utils/with_apm_span'; // https://github.com/microsoft/TypeScript/issues/34933 export interface Setup { - apmEventClient: APMEventClient; internalClient: APMInternalClient; ml?: ReturnType; config: APMConfig; @@ -45,7 +43,7 @@ export async function setupRequest({ const coreContext = await context.core; const licensingContext = await context.licensing; - const [indices, includeFrozen] = await Promise.all([ + const [indices] = await Promise.all([ getApmIndices({ savedObjectsClient: coreContext.savedObjects.client, config, @@ -59,13 +57,6 @@ export async function setupRequest({ return { indices, - apmEventClient: new APMEventClient({ - esClient: coreContext.elasticsearch.client.asCurrentUser, - debug: query._inspect, - request, - indices, - options: { includeFrozen }, - }), internalClient: await createInternalESClient({ context, request, diff --git a/x-pack/plugins/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts b/x-pack/plugins/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts index 757868425be32..6b58db02bbbbc 100644 --- a/x-pack/plugins/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts +++ b/x-pack/plugins/apm/server/lib/helpers/spans/get_is_using_service_destination_metrics.ts @@ -19,7 +19,7 @@ import { SPAN_DURATION, SPAN_NAME, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../setup_request'; +import { APMEventClient } from '../create_es_client/create_apm_event_client'; export function getProcessorEventForServiceDestinationStatistics( searchServiceDestinationMetrics: boolean @@ -54,20 +54,18 @@ export function getDocCountFieldForServiceDestinationStatistics( } export async function getIsUsingServiceDestinationMetrics({ - setup, + apmEventClient, useSpanName, kuery, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; useSpanName: boolean; kuery: string; start: number; end: number; }) { - const { apmEventClient } = setup; - async function getServiceDestinationMetricsCount( query?: QueryDslQueryContainer ) { @@ -79,7 +77,7 @@ export async function getIsUsingServiceDestinationMetrics({ }, body: { track_total_hits: 1, - size: 1, + size: 0, terminate_after: 1, query: { bool: { diff --git a/x-pack/plugins/apm/server/lib/helpers/transactions/__snapshots__/get_is_using_transaction_events.test.ts.snap b/x-pack/plugins/apm/server/lib/helpers/transactions/__snapshots__/get_is_using_transaction_events.test.ts.snap index bc4ade1230986..325ce29af118d 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transactions/__snapshots__/get_is_using_transaction_events.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/helpers/transactions/__snapshots__/get_is_using_transaction_events.test.ts.snap @@ -31,7 +31,7 @@ Object { ], }, }, - "size": 1, + "size": 0, "terminate_after": 1, "track_total_hits": 1, }, @@ -57,7 +57,7 @@ Object { ], }, }, - "size": 1, + "size": 0, "terminate_after": 1, "track_total_hits": 1, }, @@ -86,7 +86,7 @@ Array [ ], }, }, - "size": 1, + "size": 0, "terminate_after": 1, "track_total_hits": 1, }, diff --git a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.test.ts b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.test.ts index 1fac873ced7be..97b4fcbf9584c 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.test.ts @@ -63,7 +63,12 @@ describe('getIsUsingTransactionEvents', () => { it('should be false', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config } ); expect(mock.response).toBe(false); @@ -71,7 +76,12 @@ describe('getIsUsingTransactionEvents', () => { it('should not query for data', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config } ); expect(mock.spy).toHaveBeenCalledTimes(0); @@ -84,7 +94,12 @@ describe('getIsUsingTransactionEvents', () => { }; it('should be false when kuery is empty', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config } ); expect(mock.response).toBe(false); @@ -92,9 +107,10 @@ describe('getIsUsingTransactionEvents', () => { it('should be false when kuery is set and metrics data found', async () => { mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getIsUsingTransactionEvents({ - setup, + config: setup.config, + apmEventClient, kuery: 'proccessor.event: "transaction"', }), { @@ -116,9 +132,10 @@ describe('getIsUsingTransactionEvents', () => { it('should be true when kuery is set and metrics data are not found', async () => { mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getIsUsingTransactionEvents({ - setup, + config: setup.config, + apmEventClient, kuery: 'proccessor.event: "transaction"', }), { @@ -140,7 +157,12 @@ describe('getIsUsingTransactionEvents', () => { it('should not query for data when kuery is empty', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config } ); expect(mock.spy).toHaveBeenCalledTimes(0); @@ -148,9 +170,10 @@ describe('getIsUsingTransactionEvents', () => { it('should query for data when kuery is set', async () => { mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getIsUsingTransactionEvents({ - setup, + config: setup.config, + apmEventClient, kuery: 'proccessor.event: "transaction"', }), { config } @@ -167,7 +190,12 @@ describe('getIsUsingTransactionEvents', () => { it('should query for data once if metrics data found', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config, mockResponse: (request) => { @@ -187,7 +215,12 @@ describe('getIsUsingTransactionEvents', () => { it('should query for data twice if metrics data not found', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config, mockResponse: (request) => { @@ -207,7 +240,12 @@ describe('getIsUsingTransactionEvents', () => { it('should be false if metrics data are found', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config, mockResponse: (request) => { @@ -226,7 +264,12 @@ describe('getIsUsingTransactionEvents', () => { it('should be true if no metrics data are found', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config, mockResponse: (request) => { @@ -245,7 +288,12 @@ describe('getIsUsingTransactionEvents', () => { it('should be false if no metrics or transactions data are found', async () => { mock = await inspectSearchParams( - (setup) => getIsUsingTransactionEvents({ setup, kuery: '' }), + (setup, apmEventClient) => + getIsUsingTransactionEvents({ + config: setup.config, + apmEventClient, + kuery: '', + }), { config, mockResponse: () => mockResponseNoHits } ); expect(mock.response).toBe(false); diff --git a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts index 409010d3c695d..20832c70d007b 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transactions/get_is_using_transaction_events.ts @@ -8,17 +8,19 @@ import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { getSearchTransactionsEvents } from '.'; -import { Setup } from '../setup_request'; import { APMEventClient } from '../create_es_client/create_apm_event_client'; import { SearchAggregatedTransactionSetting } from '../../../../common/aggregated_transactions'; +import { APMConfig } from '../../..'; export async function getIsUsingTransactionEvents({ - setup: { config, apmEventClient }, + config, + apmEventClient, kuery, start, end, }: { - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; kuery: string; start?: number; end?: number; diff --git a/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts b/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts index b6f7dfa4083e1..3e54ee7fda79f 100644 --- a/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/transactions/index.ts @@ -37,7 +37,7 @@ export async function getHasTransactionsEvents({ body: { track_total_hits: 1, terminate_after: 1, - size: 1, + size: 0, query: { bool: { filter: [ diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_coldstart_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_coldstart_rate.ts index c16b600cda146..74419d47507a4 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_coldstart_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_coldstart_rate.ts @@ -21,13 +21,13 @@ import { getProcessorEventForTransactions, } from '../helpers/transactions'; import { getBucketSizeForAggregatedTransactions } from '../helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../helpers/setup_request'; import { calculateTransactionColdstartRate, getColdstartAggregation, getTransactionColdstartRateTimeSeries, } from '../helpers/transaction_coldstart_rate'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../helpers/create_es_client/create_apm_event_client'; export async function getColdstartRate({ environment, @@ -35,7 +35,7 @@ export async function getColdstartRate({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -46,7 +46,7 @@ export async function getColdstartRate({ serviceName: string; transactionType?: string; transactionName: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; @@ -55,8 +55,6 @@ export async function getColdstartRate({ transactionColdstartRate: Coordinate[]; average: number | null; }> { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -131,7 +129,7 @@ export async function getColdstartRatePeriods({ serviceName, transactionType, transactionName = '', - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -142,7 +140,7 @@ export async function getColdstartRatePeriods({ serviceName: string; transactionType?: string; transactionName?: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; @@ -154,7 +152,7 @@ export async function getColdstartRatePeriods({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, }; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_failed_transaction_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_failed_transaction_rate.ts index 6cebbce9d974c..8fb57d037a3c6 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_failed_transaction_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_failed_transaction_rate.ts @@ -24,13 +24,13 @@ import { getProcessorEventForTransactions, } from '../helpers/transactions'; import { getBucketSizeForAggregatedTransactions } from '../helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../helpers/setup_request'; import { calculateFailedTransactionRate, getOutcomeAggregation, getFailedTransactionRateTimeSeries, } from '../helpers/transaction_error_rate'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../helpers/create_es_client/create_apm_event_client'; export async function getFailedTransactionRate({ environment, @@ -38,7 +38,7 @@ export async function getFailedTransactionRate({ serviceName, transactionTypes, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -50,7 +50,7 @@ export async function getFailedTransactionRate({ serviceName: string; transactionTypes: string[]; transactionName?: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; @@ -60,8 +60,6 @@ export async function getFailedTransactionRate({ timeseries: Coordinate[]; average: number | null; }> { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts b/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts index f0dc94daadf0b..fabe71a874beb 100644 --- a/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts +++ b/x-pack/plugins/apm/server/routes/alerts/alerting_es_client.ts @@ -8,7 +8,13 @@ import type { ESSearchRequest, ESSearchResponse } from '@kbn/es-types'; import { RuleExecutorServices } from '@kbn/alerting-plugin/server'; -export async function alertingEsClient({ +export type APMEventESSearchRequestParams = ESSearchRequest & { + body: { size: number; track_total_hits: boolean | number }; +}; + +export async function alertingEsClient< + TParams extends APMEventESSearchRequestParams +>({ scopedClusterClient, params, }: { diff --git a/x-pack/plugins/apm/server/routes/alerts/route.ts b/x-pack/plugins/apm/server/routes/alerts/route.ts index 56e23d2712868..389f6fc87c24b 100644 --- a/x-pack/plugins/apm/server/routes/alerts/route.ts +++ b/x-pack/plugins/apm/server/routes/alerts/route.ts @@ -13,6 +13,7 @@ import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, rangeRt } from '../default_api_types'; import { AggregationType } from '../../../common/rules/apm_rule_types'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const alertParamsRt = t.intersection([ t.partial({ @@ -40,12 +41,16 @@ const transactionErrorRateChartPreview = createApmServerRoute({ handler: async ( resources ): Promise<{ errorRateChartPreview: Array<{ x: number; y: number }> }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { _inspect, ...alertParams } = params.query; const errorRateChartPreview = await getTransactionErrorRateChartPreview({ - setup, + config: setup.config, + apmEventClient, alertParams, }); @@ -60,13 +65,13 @@ const transactionErrorCountChartPreview = createApmServerRoute({ handler: async ( resources ): Promise<{ errorCountChartPreview: Array<{ x: number; y: number }> }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { _inspect, ...alertParams } = params.query; const errorCountChartPreview = await getTransactionErrorCountChartPreview({ - setup, + apmEventClient, alertParams, }); @@ -86,7 +91,10 @@ const transactionDurationChartPreview = createApmServerRoute({ data: Array<{ x: number; y: number | null }>; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; @@ -94,7 +102,8 @@ const transactionDurationChartPreview = createApmServerRoute({ const latencyChartPreview = await getTransactionDurationChartPreview({ alertParams, - setup, + config: setup.config, + apmEventClient, }); return { latencyChartPreview }; diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts index 13d0f3311f186..d82a4997ffe0e 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/anomaly/register_anomaly_rule_type.ts @@ -145,6 +145,7 @@ export function registerAnomalyRuleType({ const jobIds = mlJobs.map((job) => job.jobId); const anomalySearchParams = { body: { + track_total_hits: false, size: 0, query: { bool: { diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts index 3489ae4d91be6..fa819e268c802 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/get_error_count_chart_preview.ts @@ -10,16 +10,15 @@ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames'; import { AlertParams } from '../../route'; import { environmentQuery } from '../../../../../common/utils/environment_query'; -import { Setup } from '../../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransactionErrorCountChartPreview({ - setup, + apmEventClient, alertParams, }: { - setup: Setup; + apmEventClient: APMEventClient; alertParams: AlertParams; }) { - const { apmEventClient } = setup; const { serviceName, environment, interval, start, end } = alertParams; const query = { diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts index 93c88b84d1d37..58e475ced07fb 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts @@ -83,18 +83,19 @@ export function registerErrorCountRuleType({ producer: APM_SERVER_FEATURE_ID, minimumLicenseRequired: 'basic', isExportable: true, - executor: async ({ services, params }) => { + executor: async ({ services, params: ruleParams }) => { const config = await firstValueFrom(config$); - const ruleParams = params; const indices = await getApmIndices({ config, savedObjectsClient: services.savedObjectsClient, }); + const searchParams = { index: indices.error, - size: 0, body: { + track_total_hits: false, + size: 0, query: { bool: { filter: [ diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts index ad01f8a4c3c25..292748f3af16c 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/get_transaction_duration_chart_preview.ts @@ -21,21 +21,23 @@ import { getDurationFieldForTransactions, getProcessorEventForTransactions, } from '../../../../lib/helpers/transactions'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { ENVIRONMENT_NOT_DEFINED, getEnvironmentLabel, } from '../../../../../common/environment_filter_values'; import { averageOrPercentileAgg } from '../../average_or_percentile_agg'; +import { APMConfig } from '../../../..'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransactionDurationChartPreview({ alertParams, - setup, + config, + apmEventClient, }: { alertParams: AlertParams; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; }) { - const { apmEventClient } = setup; const { aggregationType = AggregationType.Avg, environment, @@ -46,7 +48,8 @@ export async function getTransactionDurationChartPreview({ end, } = alertParams; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config, + apmEventClient, kuery: '', }); diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts index 7a7e84414aec0..0ea099c8d4bc2 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts @@ -125,6 +125,7 @@ export function registerTransactionDurationRuleType({ const searchParams = { index, body: { + track_total_hits: false, size: 0, query: { bool: { diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts index 9921b0ce16a3f..d799e025c3453 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/get_transaction_error_rate_chart_preview.ts @@ -17,25 +17,28 @@ import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../../../lib/helpers/transactions'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { calculateFailedTransactionRate, getOutcomeAggregation, } from '../../../../lib/helpers/transaction_error_rate'; +import { APMConfig } from '../../../..'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransactionErrorRateChartPreview({ - setup, + config, + apmEventClient, alertParams, }: { - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; alertParams: AlertParams; }) { - const { apmEventClient } = setup; const { serviceName, environment, transactionType, interval, start, end } = alertParams; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config, + apmEventClient, kuery: '', start, end, diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts index 5e4e04d293cd7..15a5880345ffd 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts @@ -112,8 +112,9 @@ export function registerTransactionErrorRateRuleType({ const searchParams = { index, - size: 0, body: { + track_total_hits: false, + size: 0, query: { bool: { filter: [ diff --git a/x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.ts b/x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.ts index f0e1f67ec2bec..6de9c99cdbcd3 100644 --- a/x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.ts +++ b/x-pack/plugins/apm/server/routes/apm_routes/register_apm_server_routes.ts @@ -5,25 +5,23 @@ * 2.0. */ -import { errors } from '@elastic/elasticsearch'; import Boom from '@hapi/boom'; -import { RequestHandler } from '@kbn/core-http-server'; +import * as t from 'io-ts'; import { KibanaRequest, RouteRegistrar } from '@kbn/core/server'; -import { jsonRt, mergeRt } from '@kbn/io-ts-utils'; -import { InspectResponse } from '@kbn/observability-plugin/typings/common'; +import { errors } from '@elastic/elasticsearch'; +import agent from 'elastic-apm-node'; +import { ServerRouteRepository } from '@kbn/server-route-repository'; +import { merge } from 'lodash'; import { decodeRequestParams, parseEndpoint, routeValidationObject, - ServerRouteRepository, } from '@kbn/server-route-repository'; -import agent from 'elastic-apm-node'; -import * as t from 'io-ts'; -import { merge } from 'lodash'; -import { inspectCpuProfile } from '@kbn/adhoc-profiler'; +import { jsonRt, mergeRt } from '@kbn/io-ts-utils'; +import { InspectResponse } from '@kbn/observability-plugin/typings/common'; import { pickKeys } from '../../../common/utils/pick_keys'; -import type { ApmPluginRequestHandlerContext } from '../typings'; import { APMRouteHandlerResources, TelemetryUsageCounter } from '../typings'; +import type { ApmPluginRequestHandlerContext } from '../typings'; const inspectRt = t.exact( t.partial({ @@ -66,29 +64,6 @@ export function registerRoutes({ const router = core.setup.http.createRouter(); - function wrapRouteHandlerInProfiler( - handler: RequestHandler< - unknown, - unknown, - unknown, - ApmPluginRequestHandlerContext - > - ): RequestHandler< - unknown, - { _profile?: 'inspect' }, - unknown, - ApmPluginRequestHandlerContext - > { - return (context, request, response) => { - const { _profile } = request.query; - if (_profile === 'inspect') { - delete request.query._profile; - return inspectCpuProfile(() => handler(context, request, response)); - } - return handler(context, request, response); - }; - } - routes.forEach((route) => { const { params, endpoint, options, handler } = route; @@ -105,7 +80,7 @@ export function registerRoutes({ options, validate: routeValidationObject, }, - wrapRouteHandlerInProfiler(async (context, request, response) => { + async (context, request, response) => { if (agent.isStarted()) { agent.addLabels({ plugin: 'apm', @@ -211,7 +186,7 @@ export function registerRoutes({ // cleanup inspectableEsQueriesMap.delete(request); } - }) + } ); }); } diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation.ts index 74ef7bc56fdce..48d468c517972 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation.ts @@ -12,12 +12,11 @@ import { TRANSACTION_DURATION, } from '../../../../common/elasticsearch_fieldnames'; import type { CommonCorrelationsQueryParams } from '../../../../common/correlations/types'; - -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchDurationCorrelation = async ({ - setup, + apmEventClient, eventType, start, end, @@ -29,7 +28,7 @@ export const fetchDurationCorrelation = async ({ fractions, totalDocCount, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent; expectations: number[]; ranges: estypes.AggregationsAggregationRange[]; @@ -40,8 +39,6 @@ export const fetchDurationCorrelation = async ({ correlation: number | null; ksTest: number | null; }> => { - const { apmEventClient } = setup; - const resp = await apmEventClient.search('get_duration_correlation', { apm: { events: [eventType], diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation_with_histogram.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation_with_histogram.ts index 4fd82d431af67..a4b2b0c6284d5 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation_with_histogram.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_correlation_with_histogram.ts @@ -19,13 +19,13 @@ import { } from '../../../../common/correlations/constants'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; -import { Setup } from '../../../lib/helpers/setup_request'; import { fetchDurationCorrelation } from './fetch_duration_correlation'; import { fetchDurationRanges } from './fetch_duration_ranges'; import { getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function fetchDurationCorrelationWithHistogram({ - setup, + apmEventClient, chartType, start, end, @@ -39,7 +39,7 @@ export async function fetchDurationCorrelationWithHistogram({ totalDocCount, fieldValuePair, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; chartType: LatencyDistributionChartType; expectations: number[]; ranges: estypes.AggregationsAggregationRange[]; @@ -60,7 +60,7 @@ export async function fetchDurationCorrelationWithHistogram({ }; const { correlation, ksTest } = await fetchDurationCorrelation({ - setup, + apmEventClient, eventType, start, end, @@ -76,7 +76,7 @@ export async function fetchDurationCorrelationWithHistogram({ if (correlation !== null && ksTest !== null && !isNaN(ksTest)) { if (correlation > CORRELATION_THRESHOLD && ksTest < KS_TEST_THRESHOLD) { const { durationRanges: histogram } = await fetchDurationRanges({ - setup, + apmEventClient, chartType, start, end, diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts index 29863b52c42bb..c15f40d78d2c1 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts @@ -16,9 +16,8 @@ import { POPULATED_DOC_COUNT_SAMPLE_SIZE, } from '../../../../common/correlations/constants'; import { hasPrefixToInclude } from '../../../../common/correlations/utils'; - -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; const SUPPORTED_ES_FIELD_TYPES = [ ES_FIELD_TYPES.KEYWORD, @@ -36,7 +35,7 @@ export const shouldBeExcluded = (fieldName: string) => { }; export const fetchDurationFieldCandidates = async ({ - setup, + apmEventClient, eventType, query, start, @@ -45,13 +44,11 @@ export const fetchDurationFieldCandidates = async ({ kuery, }: CommonCorrelationsQueryParams & { query: estypes.QueryDslQueryContainer; - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent.transaction | ProcessorEvent.span; }): Promise<{ fieldCandidates: string[]; }> => { - const { apmEventClient } = setup; - // Get all supported fields const [respMapping, respRandomDoc] = await Promise.all([ apmEventClient.fieldCaps('get_field_caps', { diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_fractions.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_fractions.ts index c77399d215d51..222cce131372d 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_fractions.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_fractions.ts @@ -13,14 +13,14 @@ import { SPAN_DURATION, TRANSACTION_DURATION, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; /** * Compute the actual percentile bucket counts and actual fractions */ export const fetchDurationFractions = async ({ - setup, + apmEventClient, eventType, start, end, @@ -29,11 +29,10 @@ export const fetchDurationFractions = async ({ query, ranges, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent; ranges: estypes.AggregationsAggregationRange[]; }): Promise<{ fractions: number[]; totalDocCount: number }> => { - const { apmEventClient } = setup; const resp = await apmEventClient.search('get_duration_fractions', { apm: { events: [eventType], diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_histogram_range_steps.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_histogram_range_steps.ts index 4e7c852ca9924..7e86fc70bad79 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_histogram_range_steps.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_histogram_range_steps.ts @@ -10,9 +10,9 @@ import { scaleLog } from 'd3-scale'; import { isFiniteNumber } from '@kbn/observability-plugin/common/utils/is_finite_number'; import { CommonCorrelationsQueryParams } from '../../../../common/correlations/types'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; import { getDurationField, getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; const getHistogramRangeSteps = (min: number, max: number, steps: number) => { // A d3 based scale function as a helper to get equally distributed bins on a log scale. @@ -25,7 +25,7 @@ const getHistogramRangeSteps = (min: number, max: number, steps: number) => { export const fetchDurationHistogramRangeSteps = async ({ chartType, - setup, + apmEventClient, start, end, environment, @@ -36,7 +36,7 @@ export const fetchDurationHistogramRangeSteps = async ({ durationMaxOverride, }: CommonCorrelationsQueryParams & { chartType: LatencyDistributionChartType; - setup: Setup; + apmEventClient: APMEventClient; searchMetrics: boolean; durationMinOverride?: number; durationMaxOverride?: number; @@ -59,8 +59,6 @@ export const fetchDurationHistogramRangeSteps = async ({ }; } - const { apmEventClient } = setup; - const durationField = getDurationField(chartType, searchMetrics); // when using metrics data, ensure we filter by docs with the appropriate duration field diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_percentiles.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_percentiles.ts index 618ee3a321939..d303777b08c79 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_percentiles.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_percentiles.ts @@ -6,15 +6,15 @@ */ import { SIGNIFICANT_VALUE_DIGITS } from '../../../../common/correlations/constants'; -import { Setup } from '../../../lib/helpers/setup_request'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; import { CommonCorrelationsQueryParams } from '../../../../common/correlations/types'; import { getDurationField, getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchDurationPercentiles = async ({ chartType, - setup, + apmEventClient, start, end, environment, @@ -24,7 +24,7 @@ export const fetchDurationPercentiles = async ({ searchMetrics, }: CommonCorrelationsQueryParams & { chartType: LatencyDistributionChartType; - setup: Setup; + apmEventClient: APMEventClient; percents?: number[]; searchMetrics: boolean; }): Promise<{ @@ -63,7 +63,7 @@ export const fetchDurationPercentiles = async ({ }, }, }; - const response = await setup.apmEventClient.search( + const response = await apmEventClient.search( 'get_duration_percentiles', params ); diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_ranges.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_ranges.ts index 1aca4f7be852f..c8f2aae2e9372 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_ranges.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_duration_ranges.ts @@ -8,14 +8,14 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { sumBy } from 'lodash'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; import { Environment } from '../../../../common/environment_rt'; import { getDurationField, getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchDurationRanges = async ({ rangeSteps, - setup, + apmEventClient, start, end, environment, @@ -25,7 +25,7 @@ export const fetchDurationRanges = async ({ searchMetrics, }: { rangeSteps: number[]; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: Environment; @@ -37,7 +37,6 @@ export const fetchDurationRanges = async ({ totalDocCount: number; durationRanges: Array<{ key: number; doc_count: number }>; }> => { - const { apmEventClient } = setup; const durationField = getDurationField(chartType, searchMetrics); // when using metrics data, ensure we filter by docs with the appropriate duration field diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_failed_events_correlation_p_values.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_failed_events_correlation_p_values.ts index 0eddb2b655e3a..405ca6250e5d5 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_failed_events_correlation_p_values.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_failed_events_correlation_p_values.ts @@ -13,13 +13,13 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { EventOutcome } from '../../../../common/event_outcome'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; import { fetchDurationRanges } from './fetch_duration_ranges'; import { getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchFailedEventsCorrelationPValues = async ({ - setup, + apmEventClient, start, end, environment, @@ -28,12 +28,10 @@ export const fetchFailedEventsCorrelationPValues = async ({ rangeSteps, fieldName, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; rangeSteps: number[]; fieldName: string; }) => { - const { apmEventClient } = setup; - const chartType = LatencyDistributionChartType.failedTransactionsCorrelations; const searchMetrics = false; // failed transactions correlations does not search metrics documents const eventType = getEventType(chartType, searchMetrics); @@ -106,7 +104,7 @@ export const fetchFailedEventsCorrelationPValues = async ({ 0.25 * Math.min(Math.max((bucket.score - 13.816) / 101.314, 0), 1); const { durationRanges: histogram } = await fetchDurationRanges({ - setup, + apmEventClient, chartType, start, end, diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_field_value_pairs.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_field_value_pairs.ts index e0b12cb8bb9c8..72ffea93e2cae 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_field_value_pairs.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_field_value_pairs.ts @@ -13,11 +13,11 @@ import type { import { TERMS_SIZE } from '../../../../common/correlations/constants'; import { splitAllSettledPromises } from '../utils'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from './get_common_correlations_query'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchFieldValuePairs = async ({ - setup, + apmEventClient, fieldCandidates, eventType, start, @@ -26,12 +26,10 @@ export const fetchFieldValuePairs = async ({ kuery, query, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; fieldCandidates: string[]; eventType: ProcessorEvent; }): Promise<{ fieldValuePairs: FieldValuePair[]; errors: any[] }> => { - const { apmEventClient } = setup; - const { fulfilled: responses, rejected: errors } = splitAllSettledPromises( await Promise.allSettled( fieldCandidates.map(async (fieldName) => { diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_p_values.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_p_values.ts index 72cd6baaefec4..8f2e46b3f4d3f 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_p_values.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_p_values.ts @@ -10,6 +10,7 @@ import type { FailedTransactionsCorrelation } from '../../../../common/correlati import { CommonCorrelationsQueryParams } from '../../../../common/correlations/types'; import { LatencyDistributionChartType } from '../../../../common/latency_distribution_chart_types'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { Setup } from '../../../lib/helpers/setup_request'; import { splitAllSettledPromises, getEventType } from '../utils'; import { fetchDurationHistogramRangeSteps } from './fetch_duration_histogram_range_steps'; @@ -17,6 +18,7 @@ import { fetchFailedEventsCorrelationPValues } from './fetch_failed_events_corre export const fetchPValues = async ({ setup, + apmEventClient, start, end, environment, @@ -27,6 +29,7 @@ export const fetchPValues = async ({ fieldCandidates, }: CommonCorrelationsQueryParams & { setup: Setup; + apmEventClient: APMEventClient; durationMin?: number; durationMax?: number; fieldCandidates: string[]; @@ -36,7 +39,7 @@ export const fetchPValues = async ({ const eventType = getEventType(chartType, searchMetrics); const { rangeSteps } = await fetchDurationHistogramRangeSteps({ - setup, + apmEventClient, chartType, start, end, @@ -52,7 +55,7 @@ export const fetchPValues = async ({ await Promise.allSettled( fieldCandidates.map((fieldName) => fetchFailedEventsCorrelationPValues({ - setup, + apmEventClient, start, end, environment, diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_significant_correlations.ts b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_significant_correlations.ts index abb23fbfac0e8..3bfa96423dca4 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/fetch_significant_correlations.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/fetch_significant_correlations.ts @@ -26,9 +26,11 @@ import { fetchDurationFractions } from './fetch_duration_fractions'; import { fetchDurationHistogramRangeSteps } from './fetch_duration_histogram_range_steps'; import { fetchDurationRanges } from './fetch_duration_ranges'; import { getEventType } from '../utils'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchSignificantCorrelations = async ({ setup, + apmEventClient, start, end, environment, @@ -39,6 +41,7 @@ export const fetchSignificantCorrelations = async ({ fieldValuePairs, }: CommonCorrelationsQueryParams & { setup: Setup; + apmEventClient: APMEventClient; durationMinOverride?: number; durationMaxOverride?: number; fieldValuePairs: FieldValuePair[]; @@ -50,7 +53,7 @@ export const fetchSignificantCorrelations = async ({ const eventType = getEventType(chartType, searchMetrics); const { percentiles: percentilesRecords } = await fetchDurationPercentiles({ - setup, + apmEventClient, chartType, start, end, @@ -69,7 +72,7 @@ export const fetchSignificantCorrelations = async ({ const { expectations, ranges } = computeExpectationsAndRanges(percentiles); const { fractions, totalDocCount } = await fetchDurationFractions({ - setup, + apmEventClient, eventType, start, end, @@ -80,7 +83,7 @@ export const fetchSignificantCorrelations = async ({ }); const { rangeSteps } = await fetchDurationHistogramRangeSteps({ - setup, + apmEventClient, chartType, start, end, @@ -96,7 +99,7 @@ export const fetchSignificantCorrelations = async ({ await Promise.allSettled( fieldValuePairs.map((fieldValuePair) => fetchDurationCorrelationWithHistogram({ - setup, + apmEventClient, chartType, start, end, @@ -142,7 +145,7 @@ export const fetchSignificantCorrelations = async ({ if (latencyCorrelations.length === 0 && fallbackResult) { const { fieldName, fieldValue } = fallbackResult; const { durationRanges: histogram } = await fetchDurationRanges({ - setup, + apmEventClient, chartType, start, end, diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_boolean_field_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_boolean_field_stats.ts index 0d03843fd2088..ff1019778ad56 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_boolean_field_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_boolean_field_stats.ts @@ -11,11 +11,11 @@ import { FieldValuePair, } from '../../../../../common/correlations/types'; import { BooleanFieldStats } from '../../../../../common/correlations/field_stats_types'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from '../get_common_correlations_query'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchBooleanFieldStats = async ({ - setup, + apmEventClient, eventType, start, end, @@ -24,12 +24,10 @@ export const fetchBooleanFieldStats = async ({ field, query, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent; field: FieldValuePair; }): Promise => { - const { apmEventClient } = setup; - const { fieldName } = field; const { aggregations } = await apmEventClient.search( diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_field_value_field_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_field_value_field_stats.ts index 9208a8ad682d3..622c7b7d8952e 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_field_value_field_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_field_value_field_stats.ts @@ -14,11 +14,11 @@ import { FieldValueFieldStats, TopValueBucket, } from '../../../../../common/correlations/field_stats_types'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from '../get_common_correlations_query'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchFieldValueFieldStats = async ({ - setup, + apmEventClient, eventType, start, end, @@ -28,11 +28,9 @@ export const fetchFieldValueFieldStats = async ({ field, }: CommonCorrelationsQueryParams & { eventType: ProcessorEvent; - setup: Setup; + apmEventClient: APMEventClient; field: FieldValuePair; }): Promise => { - const { apmEventClient } = setup; - const { aggregations } = await apmEventClient.search( 'get_field_value_field_stats', { diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_fields_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_fields_stats.ts index 2cd1843c6d008..90ed9b3a92950 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_fields_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_fields_stats.ts @@ -16,10 +16,10 @@ import { FieldStats } from '../../../../../common/correlations/field_stats_types import { fetchKeywordFieldStats } from './fetch_keyword_field_stats'; import { fetchNumericFieldStats } from './fetch_numeric_field_stats'; import { fetchBooleanFieldStats } from './fetch_boolean_field_stats'; -import { Setup } from '../../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchFieldsStats = async ({ - setup, + apmEventClient, eventType, start, end, @@ -29,13 +29,12 @@ export const fetchFieldsStats = async ({ fieldsToSample, }: CommonCorrelationsQueryParams & { eventType: ProcessorEvent; - setup: Setup; + apmEventClient: APMEventClient; fieldsToSample: string[]; }): Promise<{ stats: FieldStats[]; errors: any[]; }> => { - const { apmEventClient } = setup; const stats: FieldStats[] = []; const errors: any[] = []; @@ -68,7 +67,7 @@ export const fetchFieldsStats = async ({ case ES_FIELD_TYPES.KEYWORD: case ES_FIELD_TYPES.IP: return fetchKeywordFieldStats({ - setup, + apmEventClient, eventType, start, end, @@ -91,7 +90,7 @@ export const fetchFieldsStats = async ({ case ES_FIELD_TYPES.UNSIGNED_LONG: case ES_FIELD_TYPES.BYTE: return fetchNumericFieldStats({ - setup, + apmEventClient, eventType, start, end, @@ -104,7 +103,7 @@ export const fetchFieldsStats = async ({ break; case ES_FIELD_TYPES.BOOLEAN: return fetchBooleanFieldStats({ - setup, + apmEventClient, eventType, start, end, diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_keyword_field_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_keyword_field_stats.ts index 30e88c0eb8efb..7127db07721e7 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_keyword_field_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_keyword_field_stats.ts @@ -11,11 +11,11 @@ import { FieldValuePair, } from '../../../../../common/correlations/types'; import { KeywordFieldStats } from '../../../../../common/correlations/field_stats_types'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { getCommonCorrelationsQuery } from '../get_common_correlations_query'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export const fetchKeywordFieldStats = async ({ - setup, + apmEventClient, eventType, start, end, @@ -24,12 +24,10 @@ export const fetchKeywordFieldStats = async ({ query, field, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent; field: FieldValuePair; }): Promise => { - const { apmEventClient } = setup; - const body = await apmEventClient.search('get_keyword_field_stats', { apm: { events: [eventType], diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_numeric_field_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_numeric_field_stats.ts index 04b43e09d182a..63bd2a3ea9c8b 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_numeric_field_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/fetch_numeric_field_stats.ts @@ -15,11 +15,11 @@ import { CommonCorrelationsQueryParams, FieldValuePair, } from '../../../../../common/correlations/types'; -import { Setup } from '../../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; import { getCommonCorrelationsQuery } from '../get_common_correlations_query'; export const fetchNumericFieldStats = async ({ - setup, + apmEventClient, eventType, start, end, @@ -28,12 +28,10 @@ export const fetchNumericFieldStats = async ({ query, field, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; eventType: ProcessorEvent; field: FieldValuePair; }): Promise => { - const { apmEventClient } = setup; - const { fieldName } = field; const { aggregations } = await apmEventClient.search( diff --git a/x-pack/plugins/apm/server/routes/correlations/route.ts b/x-pack/plugins/apm/server/routes/correlations/route.ts index fd3a405bc7a95..4af95e7a31c1c 100644 --- a/x-pack/plugins/apm/server/routes/correlations/route.ts +++ b/x-pack/plugins/apm/server/routes/correlations/route.ts @@ -30,6 +30,7 @@ import { fetchFieldValuePairs } from './queries/fetch_field_value_pairs'; import { fetchSignificantCorrelations } from './queries/fetch_significant_correlations'; import { fetchFieldsStats } from './queries/field_stats/fetch_fields_stats'; import { fetchPValues } from './queries/fetch_p_values'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const INVALID_LICENSE = i18n.translate('xpack.apm.correlations.license.text', { defaultMessage: @@ -58,7 +59,7 @@ const fieldCandidatesTransactionsRoute = createApmServerRoute({ throw Boom.forbidden(INVALID_LICENSE); } - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { @@ -87,7 +88,7 @@ const fieldCandidatesTransactionsRoute = createApmServerRoute({ ], }, }, - setup, + apmEventClient, }); }, }); @@ -124,7 +125,7 @@ const fieldStatsTransactionsRoute = createApmServerRoute({ throw Boom.forbidden(INVALID_LICENSE); } - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { body: { @@ -140,7 +141,7 @@ const fieldStatsTransactionsRoute = createApmServerRoute({ } = resources.params; return fetchFieldsStats({ - setup, + apmEventClient, eventType: ProcessorEvent.transaction, start, end, @@ -190,7 +191,7 @@ const fieldValueStatsTransactionsRoute = createApmServerRoute({ throw Boom.forbidden(INVALID_LICENSE); } - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { @@ -207,7 +208,7 @@ const fieldValueStatsTransactionsRoute = createApmServerRoute({ } = resources.params; return fetchFieldValueFieldStats({ - setup, + apmEventClient, eventType: ProcessorEvent.transaction, start, end, @@ -262,7 +263,7 @@ const fieldValuePairsTransactionsRoute = createApmServerRoute({ throw Boom.forbidden(INVALID_LICENSE); } - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { body: { @@ -278,7 +279,7 @@ const fieldValuePairsTransactionsRoute = createApmServerRoute({ } = resources.params; return fetchFieldValuePairs({ - setup, + apmEventClient, eventType: ProcessorEvent.transaction, start, end, @@ -334,8 +335,10 @@ const significantCorrelationsTransactionsRoute = createApmServerRoute({ totalDocCount: number; fallbackResult?: import('./../../../common/correlations/latency_correlations/types').LatencyCorrelation; }> => { - const setup = await setupRequest(resources); - + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { body: { serviceName, @@ -353,6 +356,7 @@ const significantCorrelationsTransactionsRoute = createApmServerRoute({ return fetchSignificantCorrelations({ setup, + apmEventClient, start, end, environment, @@ -402,7 +406,10 @@ const pValuesTransactionsRoute = createApmServerRoute({ ccsWarning: boolean; fallbackResult?: import('./../../../common/correlations/failed_transactions_correlations/types').FailedTransactionsCorrelation; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { body: { @@ -421,6 +428,7 @@ const pValuesTransactionsRoute = createApmServerRoute({ return fetchPValues({ setup, + apmEventClient, start, end, environment, diff --git a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts index 65ecb93bcb76e..be38d78fb208f 100644 --- a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts +++ b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.test.ts @@ -11,6 +11,7 @@ import * as HistoricalAgentData from '../historical_data/has_historical_agent_da import { DataViewsService } from '@kbn/data-views-plugin/common'; import { APMRouteHandlerResources, APMCore } from '../typings'; import { APMConfig } from '../..'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; function getMockedDataViewService(existingDataViewTitle: string) { return { @@ -44,11 +45,14 @@ const coreMock = { }, } as unknown as APMCore; +const apmEventClientMock = { search: jest.fn() } as unknown as APMEventClient; + describe('createStaticDataView', () => { it(`should not create data view if 'xpack.apm.autocreateApmIndexPattern=false'`, async () => { const dataViewService = getMockedDataViewService('apm-*'); await createStaticDataView({ setup: setupMock, + apmEventClient: apmEventClientMock, resources: { config: { autoCreateApmDataView: false }, } as APMRouteHandlerResources, @@ -67,6 +71,7 @@ describe('createStaticDataView', () => { await createStaticDataView({ setup: setupMock, + apmEventClient: apmEventClientMock, resources: { config: { autoCreateApmDataView: false }, } as APMRouteHandlerResources, @@ -85,6 +90,7 @@ describe('createStaticDataView', () => { await createStaticDataView({ setup: setupMock, + apmEventClient: apmEventClientMock, resources: { core: coreMock, config: { autoCreateApmDataView: true }, @@ -107,6 +113,7 @@ describe('createStaticDataView', () => { await createStaticDataView({ setup: setupMock, + apmEventClient: apmEventClientMock, resources: { core: coreMock, config: { autoCreateApmDataView: true }, @@ -136,6 +143,7 @@ describe('createStaticDataView', () => { await createStaticDataView({ setup: setupMock, + apmEventClient: apmEventClientMock, resources: { core: coreMock, config: { autoCreateApmDataView: true }, diff --git a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts index c2310acadcff0..ff25167a0a123 100644 --- a/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts +++ b/x-pack/plugins/apm/server/routes/data_view/create_static_data_view.ts @@ -19,6 +19,7 @@ import { getApmDataViewTitle } from './get_apm_data_view_title'; import { APMRouteHandlerResources } from '../typings'; import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export type CreateDataViewResponse = Promise< | { created: boolean; dataView: DataView } @@ -29,10 +30,12 @@ export async function createStaticDataView({ dataViewService, resources, setup, + apmEventClient, }: { dataViewService: DataViewsService; resources: APMRouteHandlerResources; setup: Setup; + apmEventClient: APMEventClient; }): CreateDataViewResponse { const { config } = resources; @@ -50,7 +53,7 @@ export async function createStaticDataView({ // Discover and other apps will throw errors if an data view exists without having matching indices. // The following ensures the data view is only created if APM data is found - const hasData = await hasHistoricalAgentData(setup); + const hasData = await hasHistoricalAgentData(apmEventClient); if (!hasData) { return { diff --git a/x-pack/plugins/apm/server/routes/data_view/route.ts b/x-pack/plugins/apm/server/routes/data_view/route.ts index 6f55f67fc9b4f..8af2a522b7dcd 100644 --- a/x-pack/plugins/apm/server/routes/data_view/route.ts +++ b/x-pack/plugins/apm/server/routes/data_view/route.ts @@ -13,13 +13,17 @@ import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getApmDataViewTitle } from './get_apm_data_view_title'; import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { setupRequest } from '../../lib/helpers/setup_request'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const staticDataViewRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/data_view/static', options: { tags: ['access:apm'] }, handler: async (resources): CreateDataViewResponse => { const { context, plugins, request } = resources; - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const coreContext = await context.core; const dataViewStart = await plugins.dataViews.start(); @@ -34,6 +38,7 @@ const staticDataViewRoute = createApmServerRoute({ dataViewService, resources, setup, + apmEventClient, }); return res; diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_dependency_latency_distribution.ts b/x-pack/plugins/apm/server/routes/dependencies/get_dependency_latency_distribution.ts index 9f9e356c96dce..150ec8664a246 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_dependency_latency_distribution.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_dependency_latency_distribution.ts @@ -14,12 +14,12 @@ import { import { Environment } from '../../../common/environment_rt'; import { EventOutcome } from '../../../common/event_outcome'; import { LatencyDistributionChartType } from '../../../common/latency_distribution_chart_types'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { getOverallLatencyDistribution } from '../latency_distribution/get_overall_latency_distribution'; import { OverallLatencyDistributionResponse } from '../latency_distribution/types'; export async function getDependencyLatencyDistribution({ - setup, + apmEventClient, dependencyName, spanName, kuery, @@ -28,7 +28,7 @@ export async function getDependencyLatencyDistribution({ end, percentileThreshold, }: { - setup: Setup; + apmEventClient: APMEventClient; dependencyName: string; spanName: string; kuery: string; @@ -40,9 +40,9 @@ export async function getDependencyLatencyDistribution({ allSpansDistribution: OverallLatencyDistributionResponse; failedSpansDistribution: OverallLatencyDistributionResponse; }> { - const commonProps = { + const commonParams = { chartType: LatencyDistributionChartType.dependencyLatency, - setup, + apmEventClient, start, end, environment, @@ -62,11 +62,11 @@ export async function getDependencyLatencyDistribution({ const [allSpansDistribution, failedSpansDistribution] = await Promise.all([ getOverallLatencyDistribution({ - ...commonProps, + ...commonParams, query: commonQuery, }), getOverallLatencyDistribution({ - ...commonProps, + ...commonParams, query: { bool: { filter: [ diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_error_rate_charts_for_dependency.ts b/x-pack/plugins/apm/server/routes/dependencies/get_error_rate_charts_for_dependency.ts index d1f7a655c9f1c..135178aca2ac9 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_error_rate_charts_for_dependency.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_error_rate_charts_for_dependency.ts @@ -17,7 +17,6 @@ import { SPAN_NAME, } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; import { getMetricsDateHistogramParams } from '../../lib/helpers/metrics'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { @@ -25,11 +24,12 @@ import { getDocumentTypeFilterForServiceDestinationStatistics, getProcessorEventForServiceDestinationStatistics, } from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getErrorRateChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, environment, @@ -39,7 +39,7 @@ export async function getErrorRateChartsForDependency({ }: { dependencyName: string; spanName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: string; @@ -47,8 +47,6 @@ export async function getErrorRateChartsForDependency({ searchServiceDestinationMetrics: boolean; offset?: string; }) { - const { apmEventClient } = setup; - const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_latency_charts_for_dependency.ts b/x-pack/plugins/apm/server/routes/dependencies/get_latency_charts_for_dependency.ts index 706b0c9d59b9a..14689f9665708 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_latency_charts_for_dependency.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_latency_charts_for_dependency.ts @@ -15,7 +15,6 @@ import { SPAN_NAME, } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; import { getMetricsDateHistogramParams } from '../../lib/helpers/metrics'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { @@ -24,12 +23,13 @@ import { getLatencyFieldForServiceDestinationStatistics, getProcessorEventForServiceDestinationStatistics, } from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getLatencyChartsForDependency({ dependencyName, spanName, searchServiceDestinationMetrics, - setup, + apmEventClient, start, end, environment, @@ -39,15 +39,13 @@ export async function getLatencyChartsForDependency({ dependencyName: string; spanName: string; searchServiceDestinationMetrics: boolean; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: string; kuery: string; offset?: string; }) { - const { apmEventClient } = setup; - const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_metadata_for_dependency.ts b/x-pack/plugins/apm/server/routes/dependencies/get_metadata_for_dependency.ts index 1ebfeab5d8a0b..5daf4483f8fd0 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_metadata_for_dependency.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_metadata_for_dependency.ts @@ -9,21 +9,19 @@ import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { maybe } from '../../../common/utils/maybe'; import { SPAN_DESTINATION_SERVICE_RESOURCE } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getMetadataForDependency({ - setup, + apmEventClient, dependencyName, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; dependencyName: string; start: number; end: number; }) { - const { apmEventClient } = setup; - const sampleResponse = await apmEventClient.search( 'get_metadata_for_dependency', { diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_throughput_charts_for_dependency.ts b/x-pack/plugins/apm/server/routes/dependencies/get_throughput_charts_for_dependency.ts index 63c72f6c460d8..6ba07907666be 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_throughput_charts_for_dependency.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_throughput_charts_for_dependency.ts @@ -15,7 +15,6 @@ import { SPAN_NAME, } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { getBucketSize } from '../../lib/helpers/get_bucket_size'; import { @@ -23,11 +22,12 @@ import { getDocumentTypeFilterForServiceDestinationStatistics, getProcessorEventForServiceDestinationStatistics, } from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getThroughputChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, environment, @@ -37,7 +37,7 @@ export async function getThroughputChartsForDependency({ }: { dependencyName: string; spanName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: string; @@ -45,8 +45,6 @@ export async function getThroughputChartsForDependency({ searchServiceDestinationMetrics: boolean; offset?: string; }) { - const { apmEventClient } = setup; - const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependencies.ts b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependencies.ts index 08e2f6183303e..d8ee73af18e87 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependencies.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependencies.ts @@ -10,10 +10,10 @@ import { NodeType } from '../../../common/connections'; import { environmentQuery } from '../../../common/utils/environment_query'; import { getConnectionStats } from '../../lib/connections/get_connection_stats'; import { getConnectionStatsItemsWithRelativeImpact } from '../../lib/connections/get_connection_stats/get_connection_stats_items_with_relative_impact'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTopDependencies({ - setup, + apmEventClient, start, end, numBuckets, @@ -21,7 +21,7 @@ export async function getTopDependencies({ offset, kuery, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; numBuckets: number; @@ -30,7 +30,7 @@ export async function getTopDependencies({ kuery: string; }) { const statsItems = await getConnectionStats({ - setup, + apmEventClient, start, end, numBuckets, diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts index 3c688f9aaa488..b2e4c44a730fb 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_operations.ts @@ -24,13 +24,13 @@ import { environmentQuery } from '../../../common/utils/environment_query'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { calculateThroughputWithRange } from '../../lib/helpers/calculate_throughput'; import { getBucketSizeForAggregatedTransactions } from '../../lib/helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../../lib/helpers/setup_request'; import { getDocumentTypeFilterForServiceDestinationStatistics, getLatencyFieldForServiceDestinationStatistics, getProcessorEventForServiceDestinationStatistics, } from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; import { calculateImpactBuilder } from '../traces/calculate_impact_builder'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; const MAX_NUM_OPERATIONS = 500; @@ -47,7 +47,7 @@ export interface DependencyOperation { } export async function getTopDependencyOperations({ - setup, + apmEventClient, dependencyName, start, end, @@ -56,7 +56,7 @@ export async function getTopDependencyOperations({ kuery, searchServiceDestinationMetrics, }: { - setup: Setup; + apmEventClient: APMEventClient; dependencyName: string; start: number; end: number; @@ -65,8 +65,6 @@ export async function getTopDependencyOperations({ kuery: string; searchServiceDestinationMetrics: boolean; }) { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset, offsetInMs } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts index 6b4998517735a..5a2df933c7dba 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts @@ -30,7 +30,7 @@ import { Environment } from '../../../common/environment_rt'; import { EventOutcome } from '../../../common/event_outcome'; import { environmentQuery } from '../../../common/utils/environment_query'; import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; const MAX_NUM_SPANS = 1000; @@ -48,7 +48,7 @@ export interface DependencySpan { } export async function getTopDependencySpans({ - setup, + apmEventClient, dependencyName, spanName, start, @@ -58,7 +58,7 @@ export async function getTopDependencySpans({ sampleRangeFrom, sampleRangeTo, }: { - setup: Setup; + apmEventClient: APMEventClient; dependencyName: string; spanName: string; start: number; @@ -68,8 +68,6 @@ export async function getTopDependencySpans({ sampleRangeFrom?: number; sampleRangeTo?: number; }): Promise { - const { apmEventClient } = setup; - const spans = ( await apmEventClient.search('get_top_dependency_spans', { apm: { diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_upstream_services_for_dependency.ts b/x-pack/plugins/apm/server/routes/dependencies/get_upstream_services_for_dependency.ts index 4d620619e2eb1..8f2710cbc972f 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_upstream_services_for_dependency.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_upstream_services_for_dependency.ts @@ -10,10 +10,10 @@ import { SPAN_DESTINATION_SERVICE_RESOURCE } from '../../../common/elasticsearch import { environmentQuery } from '../../../common/utils/environment_query'; import { getConnectionStats } from '../../lib/connections/get_connection_stats'; import { getConnectionStatsItemsWithRelativeImpact } from '../../lib/connections/get_connection_stats/get_connection_stats_items_with_relative_impact'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getUpstreamServicesForDependency({ - setup, + apmEventClient, start, end, dependencyName, @@ -22,7 +22,7 @@ export async function getUpstreamServicesForDependency({ environment, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; dependencyName: string; @@ -32,7 +32,7 @@ export async function getUpstreamServicesForDependency({ offset?: string; }) { const statsItems = await getConnectionStats({ - setup, + apmEventClient, start, end, filter: [ diff --git a/x-pack/plugins/apm/server/routes/dependencies/route.ts b/x-pack/plugins/apm/server/routes/dependencies/route.ts index 1ba2e92eee57a..964e1ed95834a 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/route.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/route.ts @@ -7,7 +7,6 @@ import * as t from 'io-ts'; import { toBooleanRt, toNumberRt } from '@kbn/io-ts-utils'; -import { setupRequest } from '../../lib/helpers/setup_request'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getMetadataForDependency } from './get_metadata_for_dependency'; @@ -28,6 +27,7 @@ import { DependencySpan, getTopDependencySpans, } from './get_top_dependency_spans'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const topDependenciesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/dependencies/top_dependencies', @@ -100,11 +100,11 @@ const topDependenciesRoute = createApmServerRoute({ location: import('./../../../common/connections').Node; }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { environment, offset, numBuckets, kuery, start, end } = resources.params.query; - const opts = { setup, start, end, numBuckets, environment, kuery }; + const opts = { apmEventClient, start, end, numBuckets, environment, kuery }; const [currentDependencies, previousDependencies] = await Promise.all([ getTopDependencies(opts), @@ -198,7 +198,7 @@ const upstreamServicesForDependencyRoute = createApmServerRoute({ location: import('./../../../common/connections').Node; }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { dependencyName, @@ -213,7 +213,7 @@ const upstreamServicesForDependencyRoute = createApmServerRoute({ const opts = { dependencyName, - setup, + apmEventClient, start, end, numBuckets, @@ -264,14 +264,14 @@ const dependencyMetadataRoute = createApmServerRoute({ ): Promise<{ metadata: { spanType: string | undefined; spanSubtype: string | undefined }; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { dependencyName, start, end } = params.query; const metadata = await getMetadataForDependency({ dependencyName, - setup, + apmEventClient, start, end, }); @@ -304,7 +304,7 @@ const dependencyLatencyChartsRoute = createApmServerRoute({ currentTimeseries: Array<{ x: number; y: number }>; comparisonTimeseries: Array<{ x: number; y: number }> | null; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { dependencyName, @@ -322,7 +322,7 @@ const dependencyLatencyChartsRoute = createApmServerRoute({ dependencyName, spanName, searchServiceDestinationMetrics, - setup, + apmEventClient, start, end, kuery, @@ -333,7 +333,7 @@ const dependencyLatencyChartsRoute = createApmServerRoute({ dependencyName, spanName, searchServiceDestinationMetrics, - setup, + apmEventClient, start, end, kuery, @@ -371,7 +371,7 @@ const dependencyThroughputChartsRoute = createApmServerRoute({ currentTimeseries: Array<{ x: number; y: number | null }>; comparisonTimeseries: Array<{ x: number; y: number | null }> | null; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { dependencyName, @@ -388,7 +388,7 @@ const dependencyThroughputChartsRoute = createApmServerRoute({ getThroughputChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, kuery, @@ -399,7 +399,7 @@ const dependencyThroughputChartsRoute = createApmServerRoute({ ? getThroughputChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, kuery, @@ -438,7 +438,7 @@ const dependencyFailedTransactionRateChartsRoute = createApmServerRoute({ currentTimeseries: Array<{ x: number; y: number }>; comparisonTimeseries: Array<{ x: number; y: number }> | null; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { dependencyName, @@ -455,7 +455,7 @@ const dependencyFailedTransactionRateChartsRoute = createApmServerRoute({ getErrorRateChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, kuery, @@ -466,7 +466,7 @@ const dependencyFailedTransactionRateChartsRoute = createApmServerRoute({ ? getErrorRateChartsForDependency({ dependencyName, spanName, - setup, + apmEventClient, start, end, kuery, @@ -501,7 +501,7 @@ const dependencyOperationsRoute = createApmServerRoute({ handler: async ( resources ): Promise<{ operations: DependencyOperation[] }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { @@ -516,7 +516,7 @@ const dependencyOperationsRoute = createApmServerRoute({ } = resources.params; const operations = await getTopDependencyOperations({ - setup, + apmEventClient, dependencyName, start, end, @@ -553,7 +553,7 @@ const dependencyLatencyDistributionChartsRoute = createApmServerRoute({ allSpansDistribution: OverallLatencyDistributionResponse; failedSpansDistribution: OverallLatencyDistributionResponse; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { dependencyName, @@ -566,7 +566,7 @@ const dependencyLatencyDistributionChartsRoute = createApmServerRoute({ } = params.query; return getDependencyLatencyDistribution({ - setup, + apmEventClient, dependencyName, spanName, percentileThreshold, @@ -593,7 +593,7 @@ const topDependencySpansRoute = createApmServerRoute({ ]), }), handler: async (resources): Promise<{ spans: DependencySpan[] }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { @@ -609,7 +609,7 @@ const topDependencySpansRoute = createApmServerRoute({ } = resources.params; const spans = await getTopDependencySpans({ - setup, + apmEventClient, dependencyName, spanName, start, diff --git a/x-pack/plugins/apm/server/routes/environments/get_all_environments.test.ts b/x-pack/plugins/apm/server/routes/environments/get_all_environments.test.ts index 0f27839d94048..dd8dcfb31ba61 100644 --- a/x-pack/plugins/apm/server/routes/environments/get_all_environments.test.ts +++ b/x-pack/plugins/apm/server/routes/environments/get_all_environments.test.ts @@ -19,11 +19,11 @@ describe('getAllEnvironments', () => { }); it('fetches all environments', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getAllEnvironments({ searchAggregatedTransactions: false, serviceName: 'test', - setup, + apmEventClient, size: 50, }) ); @@ -32,12 +32,12 @@ describe('getAllEnvironments', () => { }); it('fetches all environments with includeMissing', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getAllEnvironments({ includeMissing: true, searchAggregatedTransactions: false, serviceName: 'test', - setup, + apmEventClient, size: 50, }) ); diff --git a/x-pack/plugins/apm/server/routes/environments/get_all_environments.ts b/x-pack/plugins/apm/server/routes/environments/get_all_environments.ts index 6cac07f1ea9be..8cd7c14a0d629 100644 --- a/x-pack/plugins/apm/server/routes/environments/get_all_environments.ts +++ b/x-pack/plugins/apm/server/routes/environments/get_all_environments.ts @@ -7,13 +7,13 @@ import { termQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../lib/helpers/setup_request'; import { SERVICE_NAME, SERVICE_ENVIRONMENT, } from '../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; /** * This is used for getting *all* environments, and does not filter by range. @@ -23,21 +23,19 @@ export async function getAllEnvironments({ includeMissing = false, searchAggregatedTransactions, serviceName, - setup, + apmEventClient, size, }: { includeMissing?: boolean; searchAggregatedTransactions: boolean; serviceName?: string; - setup: Setup; + apmEventClient: APMEventClient; size: number; }) { const operationName = serviceName ? 'get_all_environments_for_service' : 'get_all_environments_for_all_services'; - const { apmEventClient } = setup; - const params = { apm: { events: [ diff --git a/x-pack/plugins/apm/server/routes/environments/get_environments.test.ts b/x-pack/plugins/apm/server/routes/environments/get_environments.test.ts index 472fd9d226e35..21daac57a7521 100644 --- a/x-pack/plugins/apm/server/routes/environments/get_environments.test.ts +++ b/x-pack/plugins/apm/server/routes/environments/get_environments.test.ts @@ -19,9 +19,9 @@ describe('getEnvironments', () => { }); it('fetches environments', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getEnvironments({ - setup, + apmEventClient, serviceName: 'foo', searchAggregatedTransactions: false, size: 50, @@ -34,9 +34,9 @@ describe('getEnvironments', () => { }); it('fetches environments without a service name', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getEnvironments({ - setup, + apmEventClient, searchAggregatedTransactions: false, size: 50, start: 0, diff --git a/x-pack/plugins/apm/server/routes/environments/get_environments.ts b/x-pack/plugins/apm/server/routes/environments/get_environments.ts index dbd0eb5a9d9c6..e8a3abace204e 100644 --- a/x-pack/plugins/apm/server/routes/environments/get_environments.ts +++ b/x-pack/plugins/apm/server/routes/environments/get_environments.ts @@ -13,8 +13,8 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; -import { Setup } from '../../lib/helpers/setup_request'; import { Environment } from '../../../common/environment_rt'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; /** * This is used for getting the list of environments for the environments selector, @@ -23,12 +23,12 @@ import { Environment } from '../../../common/environment_rt'; export async function getEnvironments({ searchAggregatedTransactions, serviceName, - setup, + apmEventClient, size, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; serviceName?: string; searchAggregatedTransactions: boolean; size: number; @@ -39,8 +39,6 @@ export async function getEnvironments({ ? 'get_environments_for_service' : 'get_environments'; - const { apmEventClient } = setup; - const params = { apm: { events: [ diff --git a/x-pack/plugins/apm/server/routes/environments/route.ts b/x-pack/plugins/apm/server/routes/environments/route.ts index 4a7755230754c..6b0225caa9b36 100644 --- a/x-pack/plugins/apm/server/routes/environments/route.ts +++ b/x-pack/plugins/apm/server/routes/environments/route.ts @@ -12,6 +12,7 @@ import { setupRequest } from '../../lib/helpers/setup_request'; import { getEnvironments } from './get_environments'; import { rangeRt } from '../default_api_types'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const environmentsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/environments', @@ -36,11 +37,14 @@ const environmentsRoute = createApmServerRoute({ > >; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { context, params } = resources; const { serviceName, start, end } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -51,7 +55,7 @@ const environmentsRoute = createApmServerRoute({ maxSuggestions ); const environments = await getEnvironments({ - setup, + apmEventClient, serviceName, searchAggregatedTransactions, size, diff --git a/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.test.ts b/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.test.ts index 6d460e5d42d7b..4a3fc6d969cd0 100644 --- a/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.test.ts +++ b/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.test.ts @@ -6,7 +6,6 @@ */ import { getBuckets } from './get_buckets'; -import { APMConfig } from '../../..'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; describe('get buckets', () => { @@ -29,30 +28,9 @@ describe('get buckets', () => { serviceName: 'myServiceName', bucketSize: 10, kuery: '', - setup: { - apmEventClient: { - search: clientSpy, - } as any, - internalClient: { - search: clientSpy, - } as any, - config: new Proxy( - {}, - { - get: () => 'myIndex', - } - ) as APMConfig, - indices: { - sourcemap: 'apm-*', - error: 'apm-*', - onboarding: 'apm-*', - span: 'apm-*', - transaction: 'apm-*', - metric: 'apm-*', - apmAgentConfigurationIndex: '.apm-agent-configuration', - apmCustomLinkIndex: '.apm-custom-link', - }, - }, + apmEventClient: { + search: clientSpy, + } as any, start: 1528113600000, end: 1528977600000, }); diff --git a/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts b/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts index bd1b070da90d7..770305df2aab2 100644 --- a/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/routes/errors/distribution/get_buckets.ts @@ -16,7 +16,7 @@ import { SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getBuckets({ environment, @@ -24,7 +24,7 @@ export async function getBuckets({ serviceName, groupId, bucketSize, - setup, + apmEventClient, start, end, }: { @@ -33,12 +33,10 @@ export async function getBuckets({ serviceName: string; groupId?: string; bucketSize: number; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { apm: { events: [ProcessorEvent.error], diff --git a/x-pack/plugins/apm/server/routes/errors/distribution/get_distribution.ts b/x-pack/plugins/apm/server/routes/errors/distribution/get_distribution.ts index f4df8ab5b7dfb..9d7116a03226d 100644 --- a/x-pack/plugins/apm/server/routes/errors/distribution/get_distribution.ts +++ b/x-pack/plugins/apm/server/routes/errors/distribution/get_distribution.ts @@ -6,10 +6,10 @@ */ import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; -import { Setup } from '../../../lib/helpers/setup_request'; import { BUCKET_TARGET_COUNT } from '../../transactions/constants'; import { getBuckets } from './get_buckets'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; function getBucketSize({ start, end }: { start: number; end: number }) { return Math.floor((end - start) / BUCKET_TARGET_COUNT); @@ -20,7 +20,7 @@ export async function getErrorDistribution({ kuery, serviceName, groupId, - setup, + apmEventClient, start, end, offset, @@ -29,7 +29,7 @@ export async function getErrorDistribution({ kuery: string; serviceName: string; groupId?: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; offset?: string; @@ -50,7 +50,7 @@ export async function getErrorDistribution({ kuery, serviceName, groupId, - setup, + apmEventClient, bucketSize, }; const currentPeriodPromise = getBuckets({ diff --git a/x-pack/plugins/apm/server/routes/errors/distribution/queries.test.ts b/x-pack/plugins/apm/server/routes/errors/distribution/queries.test.ts index bda8abc1659eb..cbb606b8a0c96 100644 --- a/x-pack/plugins/apm/server/routes/errors/distribution/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/errors/distribution/queries.test.ts @@ -20,10 +20,10 @@ describe('error distribution queries', () => { }); it('fetches an error distribution', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getErrorDistribution({ serviceName: 'serviceName', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -35,11 +35,11 @@ describe('error distribution queries', () => { }); it('fetches an error distribution with a group id', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getErrorDistribution({ serviceName: 'serviceName', groupId: 'foo', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, diff --git a/x-pack/plugins/apm/server/routes/errors/erroneous_transactions/get_top_erroneous_transactions.ts b/x-pack/plugins/apm/server/routes/errors/erroneous_transactions/get_top_erroneous_transactions.ts index d126057f515be..70ee012635b01 100644 --- a/x-pack/plugins/apm/server/routes/errors/erroneous_transactions/get_top_erroneous_transactions.ts +++ b/x-pack/plugins/apm/server/routes/errors/erroneous_transactions/get_top_erroneous_transactions.ts @@ -26,16 +26,16 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; async function getTopErroneousTransactions({ environment, kuery, serviceName, groupId, - setup, + apmEventClient, start, end, numBuckets, @@ -45,14 +45,12 @@ async function getTopErroneousTransactions({ kuery: string; serviceName: string; groupId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; numBuckets: number; offset?: string; }) { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset, offsetInMs } = getOffsetInMs({ start, end, @@ -132,7 +130,7 @@ async function getTopErroneousTransactions({ export async function getTopErroneousTransactionsPeriods({ kuery, serviceName, - setup, + apmEventClient, numBuckets, groupId, environment, @@ -142,7 +140,7 @@ export async function getTopErroneousTransactionsPeriods({ }: { kuery: string; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; numBuckets: number; groupId: string; environment: string; @@ -155,7 +153,7 @@ export async function getTopErroneousTransactionsPeriods({ environment, kuery, serviceName, - setup, + apmEventClient, numBuckets, groupId, start, @@ -166,7 +164,7 @@ export async function getTopErroneousTransactionsPeriods({ environment, kuery, serviceName, - setup, + apmEventClient, numBuckets, groupId, start, diff --git a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts index 325b134902c86..30720c2d799c3 100644 --- a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_detailed_statistics.ts @@ -20,13 +20,13 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getErrorGroupDetailedStatistics({ kuery, serviceName, - setup, + apmEventClient, numBuckets, groupIds, environment, @@ -36,7 +36,7 @@ export async function getErrorGroupDetailedStatistics({ }: { kuery: string; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; numBuckets: number; groupIds: string[]; environment: string; @@ -44,8 +44,6 @@ export async function getErrorGroupDetailedStatistics({ end: number; offset?: string; }): Promise> { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -124,7 +122,7 @@ export async function getErrorGroupDetailedStatistics({ export async function getErrorGroupPeriods({ kuery, serviceName, - setup, + apmEventClient, numBuckets, groupIds, environment, @@ -134,7 +132,7 @@ export async function getErrorGroupPeriods({ }: { kuery: string; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; numBuckets: number; groupIds: string[]; environment: string; @@ -146,7 +144,7 @@ export async function getErrorGroupPeriods({ environment, kuery, serviceName, - setup, + apmEventClient, numBuckets, groupIds, }; diff --git a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts index c27d30a253f28..f4d2fcff4a402 100644 --- a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts +++ b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_main_statistics.ts @@ -25,12 +25,12 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { getErrorName } from '../../../lib/helpers/get_error_name'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getErrorGroupMainStatistics({ kuery, serviceName, - setup, + apmEventClient, environment, sortField, sortDirection = 'desc', @@ -42,7 +42,7 @@ export async function getErrorGroupMainStatistics({ }: { kuery: string; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; environment: string; sortField?: string; sortDirection?: 'asc' | 'desc'; @@ -52,8 +52,6 @@ export async function getErrorGroupMainStatistics({ transactionName?: string; transactionType?: string; }) { - const { apmEventClient } = setup; - // sort buckets by last occurrence of error const sortByLatestOccurrence = sortField === 'lastSeen'; diff --git a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample.ts b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample.ts index d5a8b8a5ac650..744db1c9c21b8 100644 --- a/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample.ts +++ b/x-pack/plugins/apm/server/routes/errors/get_error_groups/get_error_group_sample.ts @@ -15,14 +15,14 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { getTransaction } from '../../transactions/get_transaction'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getErrorGroupSample({ environment, kuery, serviceName, groupId, - setup, + apmEventClient, start, end, }: { @@ -30,12 +30,10 @@ export async function getErrorGroupSample({ kuery: string; serviceName: string; groupId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { apm: { events: [ProcessorEvent.error as const], @@ -72,7 +70,7 @@ export async function getErrorGroupSample({ transaction = await getTransaction({ transactionId, traceId, - setup, + apmEventClient, start, end, }); diff --git a/x-pack/plugins/apm/server/routes/errors/queries.test.ts b/x-pack/plugins/apm/server/routes/errors/queries.test.ts index 7cb84db0d7862..080f175c2d5e8 100644 --- a/x-pack/plugins/apm/server/routes/errors/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/errors/queries.test.ts @@ -21,11 +21,11 @@ describe('error queries', () => { }); it('fetches a single error group', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getErrorGroupSample({ groupId: 'groupId', serviceName: 'serviceName', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -37,12 +37,12 @@ describe('error queries', () => { }); it('fetches multiple error groups', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getErrorGroupMainStatistics({ sortDirection: 'asc', sortField: 'foo', serviceName: 'serviceName', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -54,12 +54,12 @@ describe('error queries', () => { }); it('fetches multiple error groups when sortField = lastSeen', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getErrorGroupMainStatistics({ sortDirection: 'asc', sortField: 'lastSeen', serviceName: 'serviceName', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, diff --git a/x-pack/plugins/apm/server/routes/errors/route.ts b/x-pack/plugins/apm/server/routes/errors/route.ts index 17faea2765daa..5bb3bda954ee5 100644 --- a/x-pack/plugins/apm/server/routes/errors/route.ts +++ b/x-pack/plugins/apm/server/routes/errors/route.ts @@ -9,13 +9,13 @@ import { jsonRt, toNumberRt } from '@kbn/io-ts-utils'; import * as t from 'io-ts'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getErrorDistribution } from './distribution/get_distribution'; -import { setupRequest } from '../../lib/helpers/setup_request'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { getErrorGroupMainStatistics } from './get_error_groups/get_error_group_main_statistics'; import { getErrorGroupPeriods } from './get_error_groups/get_error_group_detailed_statistics'; import { getErrorGroupSample } from './get_error_groups/get_error_group_sample'; import { offsetRt } from '../../../common/comparison_rt'; import { getTopErroneousTransactionsPeriods } from './erroneous_transactions/get_top_erroneous_transactions'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const errorsMainStatisticsRoute = createApmServerRoute({ endpoint: @@ -49,7 +49,7 @@ const errorsMainStatisticsRoute = createApmServerRoute({ }>; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { serviceName } = params.path; const { environment, kuery, sortField, sortDirection, start, end } = params.query; @@ -60,7 +60,7 @@ const errorsMainStatisticsRoute = createApmServerRoute({ serviceName, sortField, sortDirection, - setup, + apmEventClient, start, end, }); @@ -102,7 +102,7 @@ const errorsMainStatisticsByTransactionNameRoute = createApmServerRoute({ }>; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { serviceName } = params.path; const { environment, @@ -118,7 +118,7 @@ const errorsMainStatisticsByTransactionNameRoute = createApmServerRoute({ environment, kuery, serviceName, - setup, + apmEventClient, start, end, maxNumberOfErrorGroups, @@ -164,7 +164,7 @@ const errorsDetailedStatisticsRoute = createApmServerRoute({ groupId: string; }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { @@ -177,7 +177,7 @@ const errorsDetailedStatisticsRoute = createApmServerRoute({ environment, kuery, serviceName, - setup, + apmEventClient, numBuckets, groupIds, start, @@ -207,7 +207,7 @@ const errorGroupsRoute = createApmServerRoute({ occurrencesCount: number; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { serviceName, groupId } = params.path; const { environment, kuery, start, end } = params.query; @@ -216,7 +216,7 @@ const errorGroupsRoute = createApmServerRoute({ groupId, kuery, serviceName, - setup, + apmEventClient, start, end, }); @@ -250,7 +250,7 @@ const errorDistributionRoute = createApmServerRoute({ }>; bucketSize: number; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { environment, kuery, groupId, start, end, offset } = params.query; @@ -259,7 +259,7 @@ const errorDistributionRoute = createApmServerRoute({ kuery, serviceName, groupId, - setup, + apmEventClient, start, end, offset, @@ -298,7 +298,7 @@ const topErroneousTransactionsRoute = createApmServerRoute({ }>; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { path: { serviceName, groupId }, @@ -310,7 +310,7 @@ const topErroneousTransactionsRoute = createApmServerRoute({ groupId, kuery, serviceName, - setup, + apmEventClient, start, end, numBuckets, diff --git a/x-pack/plugins/apm/server/routes/event_metadata/route.ts b/x-pack/plugins/apm/server/routes/event_metadata/route.ts index a057be53ef43f..02709d2c499e5 100644 --- a/x-pack/plugins/apm/server/routes/event_metadata/route.ts +++ b/x-pack/plugins/apm/server/routes/event_metadata/route.ts @@ -9,7 +9,7 @@ import * as t from 'io-ts'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getEventMetadata } from './get_event_metadata'; import { processorEventRt } from '../../../common/processor_event'; -import { setupRequest } from '../../lib/helpers/setup_request'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const eventMetadataRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/event_metadata/{processorEvent}/{id}', @@ -23,14 +23,14 @@ const eventMetadataRoute = createApmServerRoute({ handler: async ( resources ): Promise<{ metadata: Partial> }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { path: { processorEvent, id }, } = resources.params; const metadata = await getEventMetadata({ - apmEventClient: setup.apmEventClient, + apmEventClient, processorEvent, id, }); diff --git a/x-pack/plugins/apm/server/routes/fallback_to_transactions/route.ts b/x-pack/plugins/apm/server/routes/fallback_to_transactions/route.ts index 60355b5447b85..2e056df7ee891 100644 --- a/x-pack/plugins/apm/server/routes/fallback_to_transactions/route.ts +++ b/x-pack/plugins/apm/server/routes/fallback_to_transactions/route.ts @@ -10,6 +10,7 @@ import { getIsUsingTransactionEvents } from '../../lib/helpers/transactions/get_ import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { kueryRt, rangeRt } from '../default_api_types'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const fallbackToTransactionsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/fallback_to_transactions', @@ -18,7 +19,10 @@ const fallbackToTransactionsRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ fallbackToTransactions: boolean }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params: { query: { kuery, start, end }, @@ -26,7 +30,8 @@ const fallbackToTransactionsRoute = createApmServerRoute({ } = resources; return { fallbackToTransactions: await getIsUsingTransactionEvents({ - setup, + config: setup.config, + apmEventClient, kuery, start, end, diff --git a/x-pack/plugins/apm/server/routes/historical_data/has_historical_agent_data.ts b/x-pack/plugins/apm/server/routes/historical_data/has_historical_agent_data.ts index e269e036396fd..befd25abb22f2 100644 --- a/x-pack/plugins/apm/server/routes/historical_data/has_historical_agent_data.ts +++ b/x-pack/plugins/apm/server/routes/historical_data/has_historical_agent_data.ts @@ -6,12 +6,10 @@ */ import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; // Note: this logic is duplicated in tutorials/apm/envs/on_prem -export async function hasHistoricalAgentData(setup: Setup) { - const { apmEventClient } = setup; - +export async function hasHistoricalAgentData(apmEventClient: APMEventClient) { const params = { terminate_after: 1, apm: { diff --git a/x-pack/plugins/apm/server/routes/historical_data/route.ts b/x-pack/plugins/apm/server/routes/historical_data/route.ts index e9836df61acbf..204af4bb05b03 100644 --- a/x-pack/plugins/apm/server/routes/historical_data/route.ts +++ b/x-pack/plugins/apm/server/routes/historical_data/route.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { setupRequest } from '../../lib/helpers/setup_request'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { hasHistoricalAgentData } from './has_historical_agent_data'; @@ -13,8 +13,8 @@ const hasDataRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/has_data', options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ hasData: boolean }> => { - const setup = await setupRequest(resources); - const hasData = await hasHistoricalAgentData(setup); + const apmEventClient = await getApmEventClient(resources); + const hasData = await hasHistoricalAgentData(apmEventClient); return { hasData }; }, }); diff --git a/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts b/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts index 0d79901efbc72..73c9732335c10 100644 --- a/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts +++ b/x-pack/plugins/apm/server/routes/infrastructure/get_infrastructure_data.ts @@ -7,7 +7,6 @@ import { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../lib/helpers/setup_request'; import { environmentQuery } from '../../../common/utils/environment_query'; import { SERVICE_NAME, @@ -15,24 +14,23 @@ import { HOST_HOSTNAME, KUBERNETES_POD_NAME, } from '../../../common/elasticsearch_fieldnames'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export const getInfrastructureData = async ({ kuery, serviceName, environment, - setup, + apmEventClient, start, end, }: { kuery: string; serviceName: string; environment: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) => { - const { apmEventClient } = setup; - const response = await apmEventClient.search('get_service_infrastructure', { apm: { events: [ProcessorEvent.metric], diff --git a/x-pack/plugins/apm/server/routes/infrastructure/route.ts b/x-pack/plugins/apm/server/routes/infrastructure/route.ts index 678f380bc4fd8..151ed589396ce 100644 --- a/x-pack/plugins/apm/server/routes/infrastructure/route.ts +++ b/x-pack/plugins/apm/server/routes/infrastructure/route.ts @@ -6,7 +6,7 @@ */ import * as t from 'io-ts'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; -import { setupRequest } from '../../lib/helpers/setup_request'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { getInfrastructureData } from './get_infrastructure_data'; import { getContainerHostNames } from './get_host_names'; @@ -29,7 +29,7 @@ const infrastructureRoute = createApmServerRoute({ hostNames: string[]; podNames: string[]; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const infraMetricsClient = createInfraMetricsClient(resources); const { params } = resources; @@ -40,7 +40,7 @@ const infrastructureRoute = createApmServerRoute({ } = params; const infrastructureData = await getInfrastructureData({ - setup, + apmEventClient, serviceName, environment, kuery, diff --git a/x-pack/plugins/apm/server/routes/latency_distribution/get_overall_latency_distribution.ts b/x-pack/plugins/apm/server/routes/latency_distribution/get_overall_latency_distribution.ts index 206deb8a32d15..39ff3cf622dfd 100644 --- a/x-pack/plugins/apm/server/routes/latency_distribution/get_overall_latency_distribution.ts +++ b/x-pack/plugins/apm/server/routes/latency_distribution/get_overall_latency_distribution.ts @@ -7,21 +7,17 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { Environment } from '../../../common/environment_rt'; - -import { Setup } from '../../lib/helpers/setup_request'; - import { withApmSpan } from '../../utils/with_apm_span'; - import { fetchDurationRanges } from '../correlations/queries/fetch_duration_ranges'; import { fetchDurationHistogramRangeSteps } from '../correlations/queries/fetch_duration_histogram_range_steps'; - import { getPercentileThresholdValue } from './get_percentile_threshold_value'; import type { OverallLatencyDistributionResponse } from './types'; import { LatencyDistributionChartType } from '../../../common/latency_distribution_chart_types'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getOverallLatencyDistribution({ chartType, - setup, + apmEventClient, start, end, environment, @@ -33,7 +29,7 @@ export async function getOverallLatencyDistribution({ searchMetrics, }: { chartType: LatencyDistributionChartType; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: Environment; @@ -51,7 +47,7 @@ export async function getOverallLatencyDistribution({ overallLatencyDistribution.percentileThresholdValue = await getPercentileThresholdValue({ chartType, - setup, + apmEventClient, start, end, environment, @@ -70,7 +66,7 @@ export async function getOverallLatencyDistribution({ const { durationMin, durationMax, rangeSteps } = await fetchDurationHistogramRangeSteps({ chartType, - setup, + apmEventClient, start, end, environment, @@ -88,7 +84,7 @@ export async function getOverallLatencyDistribution({ // #3: get histogram chart data const { totalDocCount, durationRanges } = await fetchDurationRanges({ chartType, - setup, + apmEventClient, start, end, environment, diff --git a/x-pack/plugins/apm/server/routes/latency_distribution/get_percentile_threshold_value.ts b/x-pack/plugins/apm/server/routes/latency_distribution/get_percentile_threshold_value.ts index e24fefa572882..bb6b65c4a1735 100644 --- a/x-pack/plugins/apm/server/routes/latency_distribution/get_percentile_threshold_value.ts +++ b/x-pack/plugins/apm/server/routes/latency_distribution/get_percentile_threshold_value.ts @@ -7,11 +7,11 @@ import { CommonCorrelationsQueryParams } from '../../../common/correlations/types'; import { LatencyDistributionChartType } from '../../../common/latency_distribution_chart_types'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { fetchDurationPercentiles } from '../correlations/queries/fetch_duration_percentiles'; export async function getPercentileThresholdValue({ - setup, + apmEventClient, chartType, start, end, @@ -21,13 +21,13 @@ export async function getPercentileThresholdValue({ percentileThreshold, searchMetrics, }: CommonCorrelationsQueryParams & { - setup: Setup; + apmEventClient: APMEventClient; chartType: LatencyDistributionChartType; percentileThreshold: number; searchMetrics: boolean; }) { const durationPercentiles = await fetchDurationPercentiles({ - setup, + apmEventClient, chartType, start, end, diff --git a/x-pack/plugins/apm/server/routes/latency_distribution/route.ts b/x-pack/plugins/apm/server/routes/latency_distribution/route.ts index ac0e65abe3157..d2e6dee2795e7 100644 --- a/x-pack/plugins/apm/server/routes/latency_distribution/route.ts +++ b/x-pack/plugins/apm/server/routes/latency_distribution/route.ts @@ -23,6 +23,7 @@ import { latencyDistributionChartTypeRt, LatencyDistributionChartType, } from '../../../common/latency_distribution_chart_types'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const latencyOverallTransactionDistributionRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/latency/overall_distribution/transactions', @@ -54,7 +55,10 @@ const latencyOverallTransactionDistributionRoute = createApmServerRoute({ handler: async ( resources ): Promise => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { environment, @@ -75,7 +79,8 @@ const latencyOverallTransactionDistributionRoute = createApmServerRoute({ const searchAggregatedTransactions = chartType === LatencyDistributionChartType.transactionLatency ? await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -83,7 +88,7 @@ const latencyOverallTransactionDistributionRoute = createApmServerRoute({ : false; return getOverallLatencyDistribution({ - setup, + apmEventClient, chartType, environment, kuery, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/default.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/default.ts index b4e95d5217daa..d228cdd352981 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/default.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/default.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMConfig } from '../../..'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { getCPUChartData } from './shared/cpu'; import { getMemoryChartData } from './shared/memory'; @@ -13,19 +14,37 @@ export function getDefaultMetricsCharts({ environment, kuery, serviceName, - setup, + config, + apmEventClient, start, end, }: { environment: string; kuery: string; serviceName: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; start: number; end: number; }) { return Promise.all([ - getCPUChartData({ environment, kuery, setup, serviceName, start, end }), - getMemoryChartData({ environment, kuery, setup, serviceName, start, end }), + getCPUChartData({ + environment, + kuery, + config, + apmEventClient, + serviceName, + start, + end, + }), + getMemoryChartData({ + environment, + kuery, + config, + apmEventClient, + serviceName, + start, + end, + }), ]); } diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.test.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.test.ts index 8b3fcbfe5ce88..d6e60cf713d54 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.test.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.test.ts @@ -9,6 +9,7 @@ import { METRIC_JAVA_GC_COUNT, METRIC_JAVA_GC_TIME, } from '../../../../../../common/elasticsearch_fieldnames'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; import { Setup } from '../../../../../lib/helpers/setup_request'; import { ChartBase } from '../../../types'; @@ -50,9 +51,11 @@ describe('fetchAndTransformGcMetrics', () => { }, }; const setup = { - apmEventClient: { search: () => Promise.resolve(response) }, config: { 'xpack.gc.metricsInterval': 0 }, } as unknown as Setup; + const apmEventClient = { + search: () => Promise.resolve(response), + } as unknown as APMEventClient; const fieldName = METRIC_JAVA_GC_TIME; const { series } = await fetchAndTransformGcMetrics({ @@ -61,7 +64,8 @@ describe('fetchAndTransformGcMetrics', () => { fieldName, kuery: '', operationName: 'test operation name', - setup, + config: setup.config, + apmEventClient, serviceName: 'test service name', start: 1633456140000, end: 1633457078105, @@ -110,9 +114,11 @@ describe('fetchAndTransformGcMetrics', () => { }, }; const setup = { - apmEventClient: { search: () => Promise.resolve(response) }, config: { 'xpack.gc.metricsInterval': 0 }, } as unknown as Setup; + const apmEventClient = { + search: () => Promise.resolve(response), + } as unknown as APMEventClient; const fieldName = METRIC_JAVA_GC_COUNT; const { series } = await fetchAndTransformGcMetrics({ @@ -121,7 +127,8 @@ describe('fetchAndTransformGcMetrics', () => { fieldName, kuery: '', operationName: 'test operation name', - setup, + config: setup.config, + apmEventClient, serviceName: 'test service name', start: 1633456140000, end: 1633457078105, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts index 08f0cf52d00c0..cb97295bbca01 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/fetch_and_transform_gc_metrics.ts @@ -10,7 +10,6 @@ import { euiLightVars as theme } from '@kbn/ui-theme'; import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { isFiniteNumber } from '../../../../../../common/utils/is_finite_number'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { getMetricsDateHistogramParams } from '../../../../../lib/helpers/metrics'; import { ChartBase } from '../../../types'; @@ -28,11 +27,14 @@ import { environmentQuery, serviceNodeNameQuery, } from '../../../../../../common/utils/environment_query'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; export async function fetchAndTransformGcMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, chartBase, @@ -43,7 +45,8 @@ export async function fetchAndTransformGcMetrics({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -52,8 +55,6 @@ export async function fetchAndTransformGcMetrics({ fieldName: typeof METRIC_JAVA_GC_COUNT | typeof METRIC_JAVA_GC_TIME; operationName: string; }) { - const { apmEventClient, config } = setup; - const { bucketSize } = getBucketSize({ start, end }); // GC rate and time are reported by the agents as monotonically diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_rate_chart.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_rate_chart.ts index 0476025594b26..6fac756ccef5f 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_rate_chart.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_rate_chart.ts @@ -8,9 +8,10 @@ import { euiLightVars as theme } from '@kbn/ui-theme'; import { i18n } from '@kbn/i18n'; import { METRIC_JAVA_GC_COUNT } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { fetchAndTransformGcMetrics } from './fetch_and_transform_gc_metrics'; import { ChartBase } from '../../../types'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { [METRIC_JAVA_GC_COUNT]: { @@ -34,7 +35,8 @@ const chartBase: ChartBase = { function getGcRateChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -42,7 +44,8 @@ function getGcRateChart({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -51,7 +54,8 @@ function getGcRateChart({ return fetchAndTransformGcMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_time_chart.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_time_chart.ts index b1ef7b5e106f5..fed63945800da 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_time_chart.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/gc/get_gc_time_chart.ts @@ -8,9 +8,10 @@ import { euiLightVars as theme } from '@kbn/ui-theme'; import { i18n } from '@kbn/i18n'; import { METRIC_JAVA_GC_TIME } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { fetchAndTransformGcMetrics } from './fetch_and_transform_gc_metrics'; import { ChartBase } from '../../../types'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; +import { APMConfig } from '../../../../..'; const series = { [METRIC_JAVA_GC_TIME]: { @@ -34,7 +35,8 @@ const chartBase: ChartBase = { function getGcTimeChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -42,7 +44,8 @@ function getGcTimeChart({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -51,7 +54,8 @@ function getGcTimeChart({ return fetchAndTransformGcMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/heap_memory/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/heap_memory/index.ts index d57dfb184ca88..86c0b0af26054 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/heap_memory/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/heap_memory/index.ts @@ -13,10 +13,11 @@ import { METRIC_JAVA_HEAP_MEMORY_USED, AGENT_NAME, } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; import { ChartBase } from '../../../types'; import { JAVA_AGENT_NAMES } from '../../../../../../common/agent_name'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { heapMemoryUsed: { @@ -55,7 +56,8 @@ const chartBase: ChartBase = { export function getHeapMemoryChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -63,7 +65,8 @@ export function getHeapMemoryChart({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -72,7 +75,8 @@ export function getHeapMemoryChart({ return fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/index.ts index 5250884ed5e44..558e37af1cffc 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/index.ts @@ -7,18 +7,20 @@ import { withApmSpan } from '../../../../utils/with_apm_span'; import { getHeapMemoryChart } from './heap_memory'; -import { Setup } from '../../../../lib/helpers/setup_request'; import { getNonHeapMemoryChart } from './non_heap_memory'; import { getThreadCountChart } from './thread_count'; import { getCPUChartData } from '../shared/cpu'; import { getMemoryChartData } from '../shared/memory'; import { getGcRateChart } from './gc/get_gc_rate_chart'; import { getGcTimeChart } from './gc/get_gc_time_chart'; +import { APMConfig } from '../../../..'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export function getJavaMetricsCharts({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -26,7 +28,8 @@ export function getJavaMetricsCharts({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -36,7 +39,8 @@ export function getJavaMetricsCharts({ const options = { environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/non_heap_memory/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/non_heap_memory/index.ts index 379962d928e28..888af8d795afb 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/non_heap_memory/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/non_heap_memory/index.ts @@ -13,10 +13,11 @@ import { METRIC_JAVA_NON_HEAP_MEMORY_USED, AGENT_NAME, } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { ChartBase } from '../../../types'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; import { JAVA_AGENT_NAMES } from '../../../../../../common/agent_name'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { nonHeapMemoryUsed: { @@ -52,7 +53,8 @@ const chartBase: ChartBase = { export async function getNonHeapMemoryChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -60,7 +62,8 @@ export async function getNonHeapMemoryChart({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -69,7 +72,8 @@ export async function getNonHeapMemoryChart({ return fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/thread_count/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/thread_count/index.ts index b9a49acb3d16e..74daa14438a15 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/java/thread_count/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/java/thread_count/index.ts @@ -11,10 +11,11 @@ import { METRIC_JAVA_THREAD_COUNT, AGENT_NAME, } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { ChartBase } from '../../../types'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; import { JAVA_AGENT_NAMES } from '../../../../../../common/agent_name'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { threadCount: { @@ -44,7 +45,8 @@ const chartBase: ChartBase = { export async function getThreadCountChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -52,7 +54,8 @@ export async function getThreadCountChart({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -61,7 +64,8 @@ export async function getThreadCountChart({ return fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/active_instances.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/active_instances.ts deleted file mode 100644 index 02cbfc3b5704c..0000000000000 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/active_instances.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 { i18n } from '@kbn/i18n'; -import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; -import { euiLightVars as theme } from '@kbn/ui-theme'; -import { - SERVICE_NAME, - SERVICE_NODE_NAME, -} from '../../../../../common/elasticsearch_fieldnames'; -import { environmentQuery } from '../../../../../common/utils/environment_query'; -import { getMetricsDateHistogramParams } from '../../../../lib/helpers/metrics'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { - getDocumentTypeFilterForTransactions, - getProcessorEventForTransactions, -} from '../../../../lib/helpers/transactions'; -import { GenericMetricsChart } from '../../fetch_and_transform_metrics'; - -export async function getActiveInstances({ - environment, - kuery, - setup, - serviceName, - start, - end, - searchAggregatedTransactions, -}: { - environment: string; - kuery: string; - setup: Setup; - serviceName: string; - start: number; - end: number; - searchAggregatedTransactions: boolean; -}): Promise { - const { apmEventClient, config } = setup; - - const aggs = { - activeInstances: { - cardinality: { - field: SERVICE_NODE_NAME, - }, - }, - }; - - const params = { - apm: { - events: [getProcessorEventForTransactions(searchAggregatedTransactions)], - }, - body: { - track_total_hits: false, - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - ...rangeQuery(start, end), - ...environmentQuery(environment), - ...kqlQuery(kuery), - ...getDocumentTypeFilterForTransactions( - searchAggregatedTransactions - ), - ], - }, - }, - aggs: { - ...aggs, - timeseriesData: { - date_histogram: getMetricsDateHistogramParams({ - start, - end, - metricsInterval: config.metricsInterval, - }), - aggs, - }, - }, - }, - }; - - const { aggregations } = await apmEventClient.search( - 'get_active_instances', - params - ); - - return { - title: i18n.translate('xpack.apm.agentMetrics.serverless.activeInstances', { - defaultMessage: 'Active instances', - }), - key: 'active_instances', - yUnit: 'integer', - description: i18n.translate( - 'xpack.apm.agentMetrics.serverless.activeInstances.description', - { - defaultMessage: - 'This chart shows the number of active instances of your serverless function over time. Multiple active instances may be a result of provisioned concurrency for your function or an increase in concurrent load that scales your function on-demand. An increase in active instance can be an indicator for an increase in concurrent invocations.', - } - ), - series: [ - { - title: i18n.translate( - 'xpack.apm.agentMetrics.serverless.series.activeInstances', - { defaultMessage: 'Active instances' } - ), - key: 'active_instances', - type: 'bar', - color: theme.euiColorVis1, - overallValue: aggregations?.activeInstances.value ?? 0, - data: - aggregations?.timeseriesData.buckets.map((timeseriesBucket) => ({ - x: timeseriesBucket.key, - y: timeseriesBucket.activeInstances.value, - })) || [], - }, - ], - }; -} diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/index.ts deleted file mode 100644 index 5635dd5c0e50b..0000000000000 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/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 { withApmSpan } from '../../../../utils/with_apm_span'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { getServerlessFunctionLatency } from './serverless_function_latency'; -import { getColdStartDuration } from './cold_start_duration'; -import { getMemoryChartData } from '../shared/memory'; -import { getComputeUsage } from './compute_usage'; -import { getActiveInstances } from './active_instances'; -import { getColdStartCount } from './cold_start_count'; -import { getSearchTransactionsEvents } from '../../../../lib/helpers/transactions'; - -export function getServerlessAgentMetricCharts({ - environment, - kuery, - setup, - serviceName, - start, - end, -}: { - environment: string; - kuery: string; - setup: Setup; - serviceName: string; - start: number; - end: number; -}) { - return withApmSpan('get_serverless_agent_metric_charts', async () => { - const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, - kuery, - start, - end, - }); - - const options = { - environment, - kuery, - setup, - serviceName, - start, - end, - }; - return await Promise.all([ - getServerlessFunctionLatency({ - ...options, - searchAggregatedTransactions, - }), - getMemoryChartData(options), - getColdStartDuration(options), - getColdStartCount(options), - getComputeUsage(options), - getActiveInstances({ ...options, searchAggregatedTransactions }), - ]); - }); -} diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/cpu/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/cpu/index.ts index d09b35e25e396..7da8e16bc28cc 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/cpu/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/cpu/index.ts @@ -11,9 +11,10 @@ import { METRIC_SYSTEM_CPU_PERCENT, METRIC_PROCESS_CPU_PERCENT, } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { ChartBase } from '../../../types'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { systemCPUMax: { @@ -55,7 +56,8 @@ const chartBase: ChartBase = { export function getCPUChartData({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -63,7 +65,8 @@ export function getCPUChartData({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -72,7 +75,8 @@ export function getCPUChartData({ return fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/memory/index.ts b/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/memory/index.ts index a7e41ea71b725..c4aa4f82fe558 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/memory/index.ts +++ b/x-pack/plugins/apm/server/routes/metrics/by_agent/shared/memory/index.ts @@ -15,9 +15,10 @@ import { METRIC_SYSTEM_FREE_MEMORY, METRIC_SYSTEM_TOTAL_MEMORY, } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../../lib/helpers/setup_request'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; import { ChartBase } from '../../../types'; +import { APMConfig } from '../../../../..'; +import { APMEventClient } from '../../../../../lib/helpers/create_es_client/create_apm_event_client'; const series = { memoryUsedMax: { @@ -83,19 +84,21 @@ export const percentCgroupMemoryUsedScript = { export async function getMemoryChartData({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, - faasId, + serverlessId, start, end, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; - faasId?: string; + serverlessId?: string; start: number; end: number; }) { @@ -103,7 +106,8 @@ export async function getMemoryChartData({ const cgroupResponse = await fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -115,7 +119,7 @@ export async function getMemoryChartData({ }, additionalFilters: [ { exists: { field: METRIC_CGROUP_MEMORY_USAGE_BYTES } }, - ...termQuery(FAAS_ID, faasId), + ...termQuery(FAAS_ID, serverlessId), ], operationName: 'get_cgroup_memory_metrics_charts', }); @@ -124,7 +128,8 @@ export async function getMemoryChartData({ return await fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -137,7 +142,7 @@ export async function getMemoryChartData({ additionalFilters: [ { exists: { field: METRIC_SYSTEM_FREE_MEMORY } }, { exists: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, - ...termQuery(FAAS_ID, faasId), + ...termQuery(FAAS_ID, serverlessId), ], operationName: 'get_system_memory_metrics_charts', }); diff --git a/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts b/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts index 31ca4f54d932d..c23950b311e24 100644 --- a/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts +++ b/x-pack/plugins/apm/server/routes/metrics/fetch_and_transform_metrics.ts @@ -12,9 +12,11 @@ import type { AggregationOptionsByType } from '@kbn/es-types'; import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { getVizColorForIndex } from '../../../common/viz_colors'; -import { APMEventESSearchRequest } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { + APMEventClient, + APMEventESSearchRequest, +} from '../../lib/helpers/create_es_client/create_apm_event_client'; import { getMetricsDateHistogramParams } from '../../lib/helpers/metrics'; -import { Setup } from '../../lib/helpers/setup_request'; import { ChartBase } from './types'; import { environmentQuery, @@ -22,6 +24,7 @@ import { } from '../../../common/utils/environment_query'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { ChartType, Coordinate, YUnit } from '../../../typings/timeseries'; +import { APMConfig } from '../..'; type MetricsAggregationMap = Unionize<{ min: AggregationOptionsByType['min']; @@ -63,7 +66,8 @@ export interface FetchAndTransformMetrics { export async function fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, start, @@ -75,7 +79,8 @@ export async function fetchAndTransformMetrics({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; start: number; @@ -85,8 +90,6 @@ export async function fetchAndTransformMetrics({ additionalFilters?: QueryDslQueryContainer[]; operationName: string; }): Promise { - const { apmEventClient, config } = setup; - const params: GenericMetricsRequest = { apm: { events: [ProcessorEvent.metric], diff --git a/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts b/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts index b5ae2bbe093ae..afe1e6e919a26 100644 --- a/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts +++ b/x-pack/plugins/apm/server/routes/metrics/get_metrics_chart_data_by_agent.ts @@ -5,54 +5,50 @@ * 2.0. */ -import { Setup } from '../../lib/helpers/setup_request'; import { getJavaMetricsCharts } from './by_agent/java'; import { getDefaultMetricsCharts } from './by_agent/default'; -import { isJavaAgentName, isServerlessAgent } from '../../../common/agent_name'; +import { isJavaAgentName } from '../../../common/agent_name'; import { GenericMetricsChart } from './fetch_and_transform_metrics'; -import { getServerlessAgentMetricCharts } from './by_agent/serverless'; +import { APMConfig } from '../..'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getMetricsChartDataByAgent({ environment, kuery, - setup, + config, + apmEventClient, serviceName, serviceNodeName, agentName, start, end, - serviceRuntimeName, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; serviceNodeName?: string; agentName: string; start: number; end: number; - serviceRuntimeName?: string; }): Promise { const options = { environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, }; - const serverlessAgent = isServerlessAgent(serviceRuntimeName); - if (isJavaAgentName(agentName) && !serverlessAgent) { + if (isJavaAgentName(agentName)) { return getJavaMetricsCharts({ ...options, serviceNodeName, }); } - if (serverlessAgent) { - return getServerlessAgentMetricCharts(options); - } - return getDefaultMetricsCharts(options); } diff --git a/x-pack/plugins/apm/server/routes/metrics/get_service_nodes.ts b/x-pack/plugins/apm/server/routes/metrics/get_service_nodes.ts index e22ae93b25d53..2dd719717be00 100644 --- a/x-pack/plugins/apm/server/routes/metrics/get_service_nodes.ts +++ b/x-pack/plugins/apm/server/routes/metrics/get_service_nodes.ts @@ -21,25 +21,23 @@ import { SERVICE_NODE_NAME, } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; const getServiceNodes = async ({ kuery, - setup, + apmEventClient, serviceName, environment, start, end, }: { kuery: string; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; environment: string; start: number; end: number; }) => { - const { apmEventClient } = setup; - const params = { apm: { events: [ProcessorEvent.metric], diff --git a/x-pack/plugins/apm/server/routes/metrics/queries.test.ts b/x-pack/plugins/apm/server/routes/metrics/queries.test.ts index af36a030157c0..ea14dc2082921 100644 --- a/x-pack/plugins/apm/server/routes/metrics/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/metrics/queries.test.ts @@ -22,9 +22,10 @@ describe('metrics queries', () => { const createTests = (serviceNodeName?: string) => { it('fetches cpu chart data', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getCPUChartData({ - setup, + config: setup.config, + apmEventClient, serviceName: 'foo', serviceNodeName, environment: ENVIRONMENT_ALL.value, @@ -38,9 +39,10 @@ describe('metrics queries', () => { }); it('fetches memory chart data', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getMemoryChartData({ - setup, + config: setup.config, + apmEventClient, serviceName: 'foo', serviceNodeName, environment: ENVIRONMENT_ALL.value, @@ -54,9 +56,10 @@ describe('metrics queries', () => { }); it('fetches heap memory chart data', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getHeapMemoryChart({ - setup, + config: setup.config, + apmEventClient, serviceName: 'foo', serviceNodeName, environment: ENVIRONMENT_ALL.value, @@ -70,9 +73,10 @@ describe('metrics queries', () => { }); it('fetches non heap memory chart data', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getNonHeapMemoryChart({ - setup, + config: setup.config, + apmEventClient, serviceName: 'foo', serviceNodeName, environment: ENVIRONMENT_ALL.value, @@ -86,9 +90,10 @@ describe('metrics queries', () => { }); it('fetches thread count chart data', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getThreadCountChart({ - setup, + config: setup.config, + apmEventClient, serviceName: 'foo', serviceNodeName, environment: ENVIRONMENT_ALL.value, diff --git a/x-pack/plugins/apm/server/routes/metrics/route.ts b/x-pack/plugins/apm/server/routes/metrics/route.ts index 8aea9cc5a981c..8fd878222fc52 100644 --- a/x-pack/plugins/apm/server/routes/metrics/route.ts +++ b/x-pack/plugins/apm/server/routes/metrics/route.ts @@ -6,12 +6,14 @@ */ import * as t from 'io-ts'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { FetchAndTransformMetrics } from './fetch_and_transform_metrics'; import { getMetricsChartDataByAgent } from './get_metrics_chart_data_by_agent'; import { getServiceNodes } from './get_service_nodes'; +import { metricsServerlessRouteRepository } from './serverless/route'; const metricsChartsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services/{serviceName}/metrics/charts', @@ -25,7 +27,6 @@ const metricsChartsRoute = createApmServerRoute({ }), t.partial({ serviceNodeName: t.string, - serviceRuntimeName: t.string, }), environmentRt, kueryRt, @@ -39,28 +40,24 @@ const metricsChartsRoute = createApmServerRoute({ charts: FetchAndTransformMetrics[]; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { serviceName } = params.path; - const { - agentName, - environment, - kuery, - serviceNodeName, - start, - end, - serviceRuntimeName, - } = params.query; + const { agentName, environment, kuery, serviceNodeName, start, end } = + params.query; const charts = await getMetricsChartDataByAgent({ environment, kuery, - setup, + config: setup.config, + apmEventClient, serviceName, agentName, serviceNodeName, start, end, - serviceRuntimeName, }); return { charts }; @@ -88,14 +85,14 @@ const serviceMetricsJvm = createApmServerRoute({ threadCount: number | null; }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { kuery, environment, start, end } = params.query; const serviceNodes = await getServiceNodes({ kuery, - setup, + apmEventClient, serviceName, environment, start, @@ -108,4 +105,5 @@ const serviceMetricsJvm = createApmServerRoute({ export const metricsRouteRepository = { ...metricsChartsRoute, ...serviceMetricsJvm, + ...metricsServerlessRouteRepository, }; diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_overview.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_overview.ts new file mode 100644 index 0000000000000..5361070e50c8d --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_overview.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 { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { + FAAS_BILLED_DURATION, + FAAS_DURATION, + FAAS_ID, + METRICSET_NAME, + METRIC_SYSTEM_FREE_MEMORY, + METRIC_SYSTEM_TOTAL_MEMORY, + SERVICE_NAME, + SERVICE_NODE_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { getServerlessFunctionNameFromId } from '../../../../common/serverless'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { Coordinate } from '../../../../typings/timeseries'; +import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; +import { calcMemoryUsed } from './helper'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +interface ActiveInstanceTimeseries { + serverlessDuration: Coordinate[]; + billedDuration: Coordinate[]; +} + +export interface ActiveInstanceOverview { + activeInstanceName: string; + serverlessId: string; + serverlessFunctionName: string; + timeseries: ActiveInstanceTimeseries; + serverlessDurationAvg: number | null; + billedDurationAvg: number | null; + avgMemoryUsed?: number | null; + memorySize: number | null; +} + +export async function getServerlessActiveInstancesOverview({ + end, + environment, + kuery, + serviceName, + start, + serverlessId, + apmEventClient, +}: { + environment: string; + kuery: string; + serviceName: string; + start: number; + end: number; + serverlessId?: string; + apmEventClient: APMEventClient; +}) { + const { intervalString } = getBucketSize({ + start, + end, + numBuckets: 20, + }); + + const aggs = { + faasDurationAvg: { avg: { field: FAAS_DURATION } }, + faasBilledDurationAvg: { avg: { field: FAAS_BILLED_DURATION } }, + }; + + const params = { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + track_total_hits: 1, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(METRICSET_NAME, 'app'), + { term: { [SERVICE_NAME]: serviceName } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ...termQuery(FAAS_ID, serverlessId), + ], + }, + }, + aggs: { + activeInstances: { + terms: { field: SERVICE_NODE_NAME }, + aggs: { + serverlessFunctions: { + terms: { field: FAAS_ID }, + aggs: { + ...{ + ...aggs, + maxTotalMemory: { + max: { field: METRIC_SYSTEM_TOTAL_MEMORY }, + }, + avgTotalMemory: { + avg: { field: METRIC_SYSTEM_TOTAL_MEMORY }, + }, + avgFreeMemory: { avg: { field: METRIC_SYSTEM_FREE_MEMORY } }, + }, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + min_doc_count: 0, + extended_bounds: { + min: start, + max: end, + }, + }, + aggs, + }, + }, + }, + }, + }, + }, + }, + }; + + const response = await apmEventClient.search( + 'ger_serverless_active_instances_overview', + params + ); + + return ( + response.aggregations?.activeInstances?.buckets?.flatMap((bucket) => { + const activeInstanceName = bucket.key as string; + const serverlessFunctionsDetails = + bucket.serverlessFunctions.buckets.reduce( + (acc, curr) => { + const currentServerlessId = curr.key as string; + + const timeseries = + curr.timeseries.buckets.reduce( + (timeseriesAcc, timeseriesCurr) => { + return { + serverlessDuration: [ + ...timeseriesAcc.serverlessDuration, + { + x: timeseriesCurr.key, + y: timeseriesCurr.faasDurationAvg.value, + }, + ], + billedDuration: [ + ...timeseriesAcc.billedDuration, + { + x: timeseriesCurr.key, + y: timeseriesCurr.faasBilledDurationAvg.value, + }, + ], + }; + }, + { + serverlessDuration: [], + billedDuration: [], + } + ); + return [ + ...acc, + { + activeInstanceName, + serverlessId: currentServerlessId, + serverlessFunctionName: + getServerlessFunctionNameFromId(currentServerlessId), + timeseries, + serverlessDurationAvg: curr.faasDurationAvg.value, + billedDurationAvg: curr.faasBilledDurationAvg.value, + avgMemoryUsed: calcMemoryUsed({ + memoryFree: curr.avgFreeMemory.value, + memoryTotal: curr.avgTotalMemory.value, + }), + memorySize: curr.avgTotalMemory.value, + }, + ]; + }, + [] + ); + return serverlessFunctionsDetails; + }) || [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_timeseries.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_timeseries.ts new file mode 100644 index 0000000000000..facd270aec728 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_active_instances_timeseries.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + FAAS_ID, + METRICSET_NAME, + SERVICE_NAME, + SERVICE_NODE_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { Coordinate } from '../../../../typings/timeseries'; +import { getMetricsDateHistogramParams } from '../../../lib/helpers/metrics'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; +import { APMConfig } from '../../..'; + +export async function getActiveInstancesTimeseries({ + environment, + kuery, + serviceName, + start, + end, + serverlessId, + config, + apmEventClient, +}: { + environment: string; + kuery: string; + serviceName: string; + start: number; + end: number; + serverlessId?: string; + config: APMConfig; + apmEventClient: APMEventClient; +}): Promise { + const aggs = { + activeInstances: { + cardinality: { + field: SERVICE_NODE_NAME, + }, + }, + }; + + const params = { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(METRICSET_NAME, 'app'), + { term: { [SERVICE_NAME]: serviceName } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ...termQuery(FAAS_ID, serverlessId), + ], + }, + }, + aggs: { + ...aggs, + timeseriesData: { + date_histogram: getMetricsDateHistogramParams({ + start, + end, + metricsInterval: config.metricsInterval, + }), + aggs, + }, + }, + }, + }; + + const { aggregations } = await apmEventClient.search( + 'get_active_instances', + params + ); + + return ( + aggregations?.timeseriesData?.buckets?.map((timeseriesBucket) => ({ + x: timeseriesBucket.key, + y: timeseriesBucket.activeInstances.value, + })) || [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_count.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_count_chart.ts similarity index 66% rename from x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_count.ts rename to x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_count_chart.ts index d884aa8dce446..a56e6c4c8764c 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_count.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_count_chart.ts @@ -8,17 +8,19 @@ import { i18n } from '@kbn/i18n'; import { termQuery } from '@kbn/observability-plugin/server'; import { euiLightVars as theme } from '@kbn/ui-theme'; +import { APMConfig } from '../../..'; import { FAAS_COLDSTART, + FAAS_ID, METRICSET_NAME, -} from '../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { fetchAndTransformMetrics } from '../../fetch_and_transform_metrics'; -import { ChartBase } from '../../types'; +} from '../../../../common/elasticsearch_fieldnames'; +import { fetchAndTransformMetrics } from '../fetch_and_transform_metrics'; +import { ChartBase } from '../types'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; const chartBase: ChartBase = { - title: i18n.translate('xpack.apm.agentMetrics.serverless.coldStart', { - defaultMessage: 'Cold start', + title: i18n.translate('xpack.apm.agentMetrics.serverless.coldStart.title', { + defaultMessage: 'Cold starts', }), key: 'cold_start_count', type: 'bar', @@ -33,25 +35,30 @@ const chartBase: ChartBase = { }, }; -export function getColdStartCount({ +export function getColdStartCountChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, + serverlessId, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; + serverlessId?: string; }) { return fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, @@ -59,7 +66,8 @@ export function getColdStartCount({ aggs: { coldStart: { sum: { field: FAAS_COLDSTART } } }, additionalFilters: [ ...termQuery(FAAS_COLDSTART, true), - ...termQuery(METRICSET_NAME, 'transaction'), + ...termQuery(FAAS_ID, serverlessId), + ...termQuery(METRICSET_NAME, 'app'), ], operationName: 'get_cold_start_count', }); diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_duration.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_duration_chart.ts similarity index 60% rename from x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_duration.ts rename to x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_duration_chart.ts index 5a78f5d97d5e8..b1802edb5440b 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/cold_start_duration.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_cold_start_duration_chart.ts @@ -7,11 +7,17 @@ import { i18n } from '@kbn/i18n'; import { euiLightVars as theme } from '@kbn/ui-theme'; -import { FAAS_COLDSTART_DURATION } from '../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { fetchAndTransformMetrics } from '../../fetch_and_transform_metrics'; -import { ChartBase } from '../../types'; -import { isFiniteNumber } from '../../../../../common/utils/is_finite_number'; +import { termQuery } from '@kbn/observability-plugin/server'; +import { + FAAS_COLDSTART_DURATION, + FAAS_ID, + METRICSET_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { fetchAndTransformMetrics } from '../fetch_and_transform_metrics'; +import { ChartBase } from '../types'; +import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; +import { APMConfig } from '../../..'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; const chartBase: ChartBase = { title: i18n.translate('xpack.apm.agentMetrics.serverless.coldStartDuration', { @@ -38,37 +44,46 @@ const chartBase: ChartBase = { ), }; -export async function getColdStartDuration({ +export async function getColdStartDurationChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, + serverlessId, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; + serverlessId?: string; }) { const coldStartDurationMetric = await fetchAndTransformMetrics({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, chartBase, aggs: { coldStart: { avg: { field: FAAS_COLDSTART_DURATION } } }, - additionalFilters: [{ exists: { field: FAAS_COLDSTART_DURATION } }], + additionalFilters: [ + { exists: { field: FAAS_COLDSTART_DURATION } }, + ...termQuery(FAAS_ID, serverlessId), + ...termQuery(METRICSET_NAME, 'app'), + ], operationName: 'get_cold_start_duration', }); const [series] = coldStartDurationMetric.series; - const data = series.data.map(({ x, y }) => ({ + const data = series?.data?.map(({ x, y }) => ({ x, // Cold start duration duration is stored in ms, convert it to microseconds so it uses the same unit as the other charts y: isFiniteNumber(y) ? y * 1000 : y, @@ -76,13 +91,15 @@ export async function getColdStartDuration({ return { ...coldStartDurationMetric, - series: [ - { - ...series, - // Cold start duration duration is stored in ms, convert it to microseconds - overallValue: series.overallValue * 1000, - data, - }, - ], + series: series + ? [ + { + ...series, + // Cold start duration duration is stored in ms, convert it to microseconds + overallValue: series.overallValue * 1000, + data, + }, + ] + : [], }; } diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/compute_usage.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_compute_usage_chart.ts similarity index 85% rename from x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/compute_usage.ts rename to x-pack/plugins/apm/server/routes/metrics/serverless/get_compute_usage_chart.ts index bb8d5023c9af2..201961113cac0 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/compute_usage.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_compute_usage_chart.ts @@ -13,17 +13,19 @@ import { termQuery, } from '@kbn/observability-plugin/server'; import { euiLightVars as theme } from '@kbn/ui-theme'; +import { APMConfig } from '../../..'; import { FAAS_BILLED_DURATION, + FAAS_ID, METRICSET_NAME, METRIC_SYSTEM_TOTAL_MEMORY, SERVICE_NAME, -} from '../../../../../common/elasticsearch_fieldnames'; -import { environmentQuery } from '../../../../../common/utils/environment_query'; -import { isFiniteNumber } from '../../../../../common/utils/is_finite_number'; -import { getMetricsDateHistogramParams } from '../../../../lib/helpers/metrics'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { GenericMetricsChart } from '../../fetch_and_transform_metrics'; +} from '../../../../common/elasticsearch_fieldnames'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; +import { getMetricsDateHistogramParams } from '../../../lib/helpers/metrics'; +import { GenericMetricsChart } from '../fetch_and_transform_metrics'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; /** * To calculate the compute usage we need to multiply the "system.memory.total" by "faas.billed_duration". @@ -47,23 +49,25 @@ function calculateComputeUsageGBSeconds({ return totalMemoryGB * faasBilledDurationSec; } -export async function getComputeUsage({ +export async function getComputeUsageChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, + serverlessId, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; + serverlessId?: string; }): Promise { - const { apmEventClient, config } = setup; - const aggs = { avgFaasBilledDuration: { avg: { field: FAAS_BILLED_DURATION } }, avgTotalMemory: { avg: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, @@ -85,6 +89,7 @@ export async function getComputeUsage({ ...kqlQuery(kuery), { exists: { field: FAAS_BILLED_DURATION } }, ...termQuery(METRICSET_NAME, 'app'), + ...termQuery(FAAS_ID, serverlessId), ], }, }, diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_agent_metrics_chart.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_agent_metrics_chart.ts new file mode 100644 index 0000000000000..1d1bf19b635be --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_agent_metrics_chart.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 { getSearchTransactionsEvents } from '../../../lib/helpers/transactions'; +import { withApmSpan } from '../../../utils/with_apm_span'; +import { getMemoryChartData } from '../by_agent/shared/memory'; +import { getColdStartCountChart } from './get_cold_start_count_chart'; +import { getColdStartDurationChart } from './get_cold_start_duration_chart'; +import { getComputeUsageChart } from './get_compute_usage_chart'; +import { getServerlessFunctionLatencyChart } from './get_serverless_function_latency_chart'; +import { APMConfig } from '../../..'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +export function getServerlessAgentMetricsCharts({ + environment, + kuery, + config, + apmEventClient, + serviceName, + start, + end, + serverlessId, +}: { + environment: string; + kuery: string; + config: APMConfig; + apmEventClient: APMEventClient; + serviceName: string; + start: number; + end: number; + serverlessId?: string; +}) { + return withApmSpan('get_serverless_agent_metric_charts', async () => { + const searchAggregatedTransactions = await getSearchTransactionsEvents({ + config, + apmEventClient, + kuery, + start, + end, + }); + + const options = { + environment, + kuery, + apmEventClient, + config, + serviceName, + start, + end, + serverlessId, + }; + return await Promise.all([ + getServerlessFunctionLatencyChart({ + ...options, + searchAggregatedTransactions, + }), + getMemoryChartData(options), + getColdStartDurationChart(options), + getColdStartCountChart(options), + getComputeUsageChart(options), + ]); + }); +} diff --git a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/serverless_function_latency.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_function_latency_chart.ts similarity index 57% rename from x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/serverless_function_latency.ts rename to x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_function_latency_chart.ts index 0a27c66ef036b..47593c3f48409 100644 --- a/x-pack/plugins/apm/server/routes/metrics/by_agent/serverless/serverless_function_latency.ts +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_function_latency_chart.ts @@ -7,17 +7,24 @@ import { i18n } from '@kbn/i18n'; import { euiLightVars as theme } from '@kbn/ui-theme'; -import { FAAS_BILLED_DURATION } from '../../../../../common/elasticsearch_fieldnames'; -import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; -import { isFiniteNumber } from '../../../../../common/utils/is_finite_number'; -import { getVizColorForIndex } from '../../../../../common/viz_colors'; -import { Setup } from '../../../../lib/helpers/setup_request'; -import { getLatencyTimeseries } from '../../../transactions/get_latency_charts'; +import { termQuery } from '@kbn/observability-plugin/server'; +import { isEmpty } from 'lodash'; +import { + FAAS_BILLED_DURATION, + FAAS_ID, + METRICSET_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; +import { isFiniteNumber } from '../../../../common/utils/is_finite_number'; +import { getVizColorForIndex } from '../../../../common/viz_colors'; +import { getLatencyTimeseries } from '../../transactions/get_latency_charts'; +import { APMConfig } from '../../..'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { fetchAndTransformMetrics, GenericMetricsChart, -} from '../../fetch_and_transform_metrics'; -import { ChartBase } from '../../types'; +} from '../fetch_and_transform_metrics'; +import { ChartBase } from '../types'; const billedDurationAvg = { title: i18n.translate('xpack.apm.agentMetrics.serverless.billedDurationAvg', { @@ -27,7 +34,7 @@ const billedDurationAvg = { const chartBase: ChartBase = { title: i18n.translate('xpack.apm.agentMetrics.serverless.avgDuration', { - defaultMessage: 'Avg. Duration', + defaultMessage: 'Lambda Duration', }), key: 'avg_duration', type: 'linemark', @@ -45,29 +52,32 @@ const chartBase: ChartBase = { async function getServerlessLantecySeries({ environment, kuery, - setup, + apmEventClient, serviceName, start, end, + serverlessId, searchAggregatedTransactions, }: { environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; + serverlessId?: string; searchAggregatedTransactions: boolean; }): Promise { const transactionLatency = await getLatencyTimeseries({ environment, kuery, serviceName, - setup, + apmEventClient, searchAggregatedTransactions, latencyAggregationType: LatencyAggregationType.avg, start, end, + serverlessId, }); return [ @@ -85,27 +95,32 @@ async function getServerlessLantecySeries({ ]; } -export async function getServerlessFunctionLatency({ +export async function getServerlessFunctionLatencyChart({ environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, + serverlessId, searchAggregatedTransactions, }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; + serverlessId?: string; searchAggregatedTransactions: boolean; }): Promise { const options = { environment, kuery, - setup, + config, + apmEventClient, serviceName, start, end, @@ -118,29 +133,43 @@ export async function getServerlessFunctionLatency({ aggs: { billedDurationAvg: { avg: { field: FAAS_BILLED_DURATION } }, }, - additionalFilters: [{ exists: { field: FAAS_BILLED_DURATION } }], + additionalFilters: [ + { exists: { field: FAAS_BILLED_DURATION } }, + ...termQuery(FAAS_ID, serverlessId), + ...termQuery(METRICSET_NAME, 'app'), + ], operationName: 'get_billed_duration', }), - getServerlessLantecySeries({ ...options, searchAggregatedTransactions }), + getServerlessLantecySeries({ + ...options, + serverlessId, + searchAggregatedTransactions, + }), ]); - const [series] = billedDurationMetrics.series; - const data = series.data.map(({ x, y }) => ({ - x, - // Billed duration is stored in ms, convert it to microseconds so it uses the same unit as the other chart - y: isFiniteNumber(y) ? y * 1000 : y, - })); + const series = []; + + const [billedDurationSeries] = billedDurationMetrics.series; + if (billedDurationSeries) { + const data = billedDurationSeries.data?.map(({ x, y }) => ({ + x, + // Billed duration is stored in ms, convert it to microseconds so it uses the same unit as the other chart + y: isFiniteNumber(y) ? y * 1000 : y, + })); + series.push({ + ...billedDurationSeries, + // Billed duration is stored in ms, convert it to microseconds + overallValue: billedDurationSeries.overallValue * 1000, + data: data || [], + }); + } + + if (!isEmpty(serverlessDurationSeries[0].data)) { + series.push(...serverlessDurationSeries); + } return { ...billedDurationMetrics, - series: [ - { - ...series, - // Billed duration is stored in ms, convert it to microseconds - overallValue: series.overallValue * 1000, - data, - }, - ...serverlessDurationSeries, - ], + series, }; } diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_functions_overview.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_functions_overview.ts new file mode 100644 index 0000000000000..236e950c1f13c --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_functions_overview.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + kqlQuery, + rangeQuery, + termQuery, +} from '@kbn/observability-plugin/server'; +import { + FAAS_BILLED_DURATION, + FAAS_COLDSTART, + FAAS_DURATION, + FAAS_ID, + METRICSET_NAME, + METRIC_SYSTEM_FREE_MEMORY, + METRIC_SYSTEM_TOTAL_MEMORY, + SERVICE_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { getServerlessFunctionNameFromId } from '../../../../common/serverless'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { calcMemoryUsed } from './helper'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +export async function getServerlessFunctionsOverview({ + end, + environment, + kuery, + serviceName, + start, + apmEventClient, +}: { + environment: string; + kuery: string; + serviceName: string; + start: number; + end: number; + apmEventClient: APMEventClient; +}) { + const params = { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(METRICSET_NAME, 'app'), + { term: { [SERVICE_NAME]: serviceName } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ], + }, + }, + aggs: { + serverlessFunctions: { + terms: { field: FAAS_ID }, + aggs: { + faasDurationAvg: { avg: { field: FAAS_DURATION } }, + faasBilledDurationAvg: { avg: { field: FAAS_BILLED_DURATION } }, + coldStartCount: { sum: { field: FAAS_COLDSTART } }, + maxTotalMemory: { max: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, + avgTotalMemory: { avg: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, + avgFreeMemory: { avg: { field: METRIC_SYSTEM_FREE_MEMORY } }, + }, + }, + }, + }, + }; + + const response = await apmEventClient.search( + 'ger_serverless_functions_overview', + params + ); + + const serverlessFunctionsOverview = + response.aggregations?.serverlessFunctions?.buckets?.map((bucket) => { + const serverlessId = bucket.key as string; + return { + serverlessId, + serverlessFunctionName: getServerlessFunctionNameFromId(serverlessId), + serverlessDurationAvg: bucket.faasDurationAvg.value, + billedDurationAvg: bucket.faasBilledDurationAvg.value, + coldStartCount: bucket.coldStartCount.value, + avgMemoryUsed: calcMemoryUsed({ + memoryFree: bucket.avgFreeMemory.value, + memoryTotal: bucket.avgTotalMemory.value, + }), + memorySize: bucket.maxTotalMemory.value, + }; + }); + + return serverlessFunctionsOverview || []; +} diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_summary.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_summary.ts new file mode 100644 index 0000000000000..d3f292a11b872 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/get_serverless_summary.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 { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { + termQuery, + kqlQuery, + rangeQuery, +} from '@kbn/observability-plugin/server'; +import { + FAAS_BILLED_DURATION, + FAAS_DURATION, + FAAS_ID, + METRICSET_NAME, + METRIC_SYSTEM_FREE_MEMORY, + METRIC_SYSTEM_TOTAL_MEMORY, + SERVICE_NAME, +} from '../../../../common/elasticsearch_fieldnames'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { calcMemoryUsedRate } from './helper'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; + +export async function getServerlessSummary({ + end, + environment, + kuery, + serviceName, + start, + serverlessId, + apmEventClient, +}: { + environment: string; + kuery: string; + serviceName: string; + start: number; + end: number; + serverlessId?: string; + apmEventClient: APMEventClient; +}) { + const params = { + apm: { + events: [ProcessorEvent.metric], + }, + body: { + track_total_hits: false, + size: 0, + query: { + bool: { + filter: [ + ...termQuery(METRICSET_NAME, 'app'), + { term: { [SERVICE_NAME]: serviceName } }, + ...rangeQuery(start, end), + ...environmentQuery(environment), + ...kqlQuery(kuery), + ...termQuery(FAAS_ID, serverlessId), + ], + }, + }, + aggs: { + totalFunctions: { cardinality: { field: FAAS_ID } }, + faasDurationAvg: { avg: { field: FAAS_DURATION } }, + faasBilledDurationAvg: { avg: { field: FAAS_BILLED_DURATION } }, + avgTotalMemory: { avg: { field: METRIC_SYSTEM_TOTAL_MEMORY } }, + avgFreeMemory: { avg: { field: METRIC_SYSTEM_FREE_MEMORY } }, + }, + }, + }; + + const response = await apmEventClient.search( + 'ger_serverless_summary', + params + ); + + return { + memoryUsageAvgRate: calcMemoryUsedRate({ + memoryFree: response.aggregations?.avgFreeMemory?.value, + memoryTotal: response.aggregations?.avgTotalMemory?.value, + }), + serverlessFunctionsTotal: response.aggregations?.totalFunctions?.value, + serverlessDurationAvg: response.aggregations?.faasDurationAvg?.value, + billedDurationAvg: response.aggregations?.faasBilledDurationAvg?.value, + }; +} diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/helper.test.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.test.ts new file mode 100644 index 0000000000000..c6927f36a8eb9 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.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 { calcMemoryUsed, calcMemoryUsedRate } from './helper'; +describe('calcMemoryUsed', () => { + it('returns undefined when memory values are no a number', () => { + [ + { memoryFree: null, memoryTotal: null }, + { memoryFree: undefined, memoryTotal: undefined }, + { memoryFree: 100, memoryTotal: undefined }, + { memoryFree: undefined, memoryTotal: 100 }, + ].forEach(({ memoryFree, memoryTotal }) => { + expect(calcMemoryUsed({ memoryFree, memoryTotal })).toBeUndefined(); + }); + }); + + it('returns correct memory used', () => { + expect(calcMemoryUsed({ memoryFree: 50, memoryTotal: 100 })).toBe(50); + }); +}); + +describe('calcMemoryUsedRate', () => { + it('returns undefined when memory values are no a number', () => { + [ + { memoryFree: null, memoryTotal: null }, + { memoryFree: undefined, memoryTotal: undefined }, + { memoryFree: 100, memoryTotal: undefined }, + { memoryFree: undefined, memoryTotal: 100 }, + ].forEach(({ memoryFree, memoryTotal }) => { + expect(calcMemoryUsedRate({ memoryFree, memoryTotal })).toBeUndefined(); + }); + }); + + it('returns correct memory used rate', () => { + expect(calcMemoryUsedRate({ memoryFree: 50, memoryTotal: 100 })).toBe(0.5); + }); +}); diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/helper.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.ts new file mode 100644 index 0000000000000..0c16ee101c735 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/helper.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 { isFiniteNumber } from '../../../../common/utils/is_finite_number'; + +export function calcMemoryUsedRate({ + memoryFree, + memoryTotal, +}: { + memoryFree?: number | null; + memoryTotal?: number | null; +}) { + if (!isFiniteNumber(memoryFree) || !isFiniteNumber(memoryTotal)) { + return undefined; + } + + return (memoryTotal - memoryFree) / memoryTotal; +} + +export function calcMemoryUsed({ + memoryFree, + memoryTotal, +}: { + memoryFree?: number | null; + memoryTotal?: number | null; +}) { + if (!isFiniteNumber(memoryFree) || !isFiniteNumber(memoryTotal)) { + return undefined; + } + + return memoryTotal - memoryFree; +} diff --git a/x-pack/plugins/apm/server/routes/metrics/serverless/route.ts b/x-pack/plugins/apm/server/routes/metrics/serverless/route.ts new file mode 100644 index 0000000000000..af2a0aa0834f7 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/metrics/serverless/route.ts @@ -0,0 +1,189 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; +import { setupRequest } from '../../../lib/helpers/setup_request'; +import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; +import { environmentRt, kueryRt, rangeRt } from '../../default_api_types'; +import { getServerlessAgentMetricsCharts } from './get_serverless_agent_metrics_chart'; +import { getServerlessActiveInstancesOverview } from './get_active_instances_overview'; +import { getServerlessFunctionsOverview } from './get_serverless_functions_overview'; +import { getServerlessSummary } from './get_serverless_summary'; +import { getActiveInstancesTimeseries } from './get_active_instances_timeseries'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; + +const serverlessMetricsChartsRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/services/{serviceName}/metrics/serverless/charts', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + environmentRt, + kueryRt, + rangeRt, + t.partial({ serverlessId: t.string }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise<{ + charts: Awaited>; + }> => { + const { params } = resources; + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); + + const { serviceName } = params.path; + const { environment, kuery, start, end, serverlessId } = params.query; + + const charts = await getServerlessAgentMetricsCharts({ + environment, + start, + end, + kuery, + config: setup.config, + apmEventClient, + serviceName, + serverlessId, + }); + return { charts }; + }, +}); + +const serverlessMetricsActiveInstancesRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + environmentRt, + kueryRt, + rangeRt, + t.partial({ serverlessId: t.string }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise<{ + activeInstances: Awaited< + ReturnType + >; + timeseries: Awaited>; + }> => { + const { params } = resources; + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); + + const { serviceName } = params.path; + const { environment, kuery, start, end, serverlessId } = params.query; + + const options = { + environment, + start, + end, + kuery, + setup, + serviceName, + serverlessId, + apmEventClient, + }; + + const [activeInstances, timeseries] = await Promise.all([ + getServerlessActiveInstancesOverview(options), + getActiveInstancesTimeseries({ ...options, config: setup.config }), + ]); + return { activeInstances, timeseries }; + }, +}); + +const serverlessMetricsFunctionsOverviewRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([environmentRt, kueryRt, rangeRt]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise<{ + serverlessFunctionsOverview: Awaited< + ReturnType + >; + }> => { + const { params } = resources; + const apmEventClient = await getApmEventClient(resources); + + const { serviceName } = params.path; + const { environment, kuery, start, end } = params.query; + + const serverlessFunctionsOverview = await getServerlessFunctionsOverview({ + environment, + start, + end, + kuery, + apmEventClient, + serviceName, + }); + return { serverlessFunctionsOverview }; + }, +}); + +const serverlessMetricsSummaryRoute = createApmServerRoute({ + endpoint: + 'GET /internal/apm/services/{serviceName}/metrics/serverless/summary', + params: t.type({ + path: t.type({ + serviceName: t.string, + }), + query: t.intersection([ + environmentRt, + kueryRt, + rangeRt, + t.partial({ serverlessId: t.string }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async ( + resources + ): Promise>> => { + const { params } = resources; + const apmEventClient = await getApmEventClient(resources); + + const { serviceName } = params.path; + const { environment, kuery, start, end, serverlessId } = params.query; + + return getServerlessSummary({ + environment, + start, + end, + kuery, + apmEventClient, + serviceName, + serverlessId, + }); + }, +}); + +export const metricsServerlessRouteRepository = { + ...serverlessMetricsChartsRoute, + ...serverlessMetricsSummaryRoute, + ...serverlessMetricsFunctionsOverviewRoute, + ...serverlessMetricsActiveInstancesRoute, +}; diff --git a/x-pack/plugins/apm/server/routes/observability_overview/get_service_count.ts b/x-pack/plugins/apm/server/routes/observability_overview/get_service_count.ts index ecdf1c792ffe5..315f9345bff84 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview/get_service_count.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview/get_service_count.ts @@ -8,22 +8,20 @@ import { rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceCount({ - setup, + apmEventClient, searchAggregatedTransactions, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { apm: { events: [ diff --git a/x-pack/plugins/apm/server/routes/observability_overview/get_transactions_per_minute.ts b/x-pack/plugins/apm/server/routes/observability_overview/get_transactions_per_minute.ts index cfc89a7589cdf..8f99b6a08ae84 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview/get_transactions_per_minute.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview/get_transactions_per_minute.ts @@ -11,30 +11,28 @@ import { TRANSACTION_REQUEST, } from '../../../common/transaction_types'; import { TRANSACTION_TYPE } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../lib/helpers/transactions'; import { calculateThroughputWithRange } from '../../lib/helpers/calculate_throughput'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransactionsPerMinute({ - setup, + apmEventClient, bucketSize, searchAggregatedTransactions, start, end, intervalString, }: { - setup: Setup; + apmEventClient: APMEventClient; bucketSize: number; intervalString: string; searchAggregatedTransactions: boolean; start: number; end: number; }) { - const { apmEventClient } = setup; - const { aggregations } = await apmEventClient.search( 'observability_overview_get_transactions_per_minute', { diff --git a/x-pack/plugins/apm/server/routes/observability_overview/has_data.ts b/x-pack/plugins/apm/server/routes/observability_overview/has_data.ts index 384f26b31e70c..66436caa7a405 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview/has_data.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview/has_data.ts @@ -6,10 +6,16 @@ */ import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; -export async function getHasData({ setup }: { setup: Setup }) { - const { apmEventClient } = setup; +export async function getHasData({ + indices, + apmEventClient, +}: { + indices: ApmIndicesConfig; + apmEventClient: APMEventClient; +}) { try { const params = { apm: { @@ -32,12 +38,12 @@ export async function getHasData({ setup }: { setup: Setup }) { ); return { hasData: response.hits.total.value > 0, - indices: setup.indices, + indices, }; } catch (e) { return { hasData: false, - indices: setup.indices, + indices, }; } } diff --git a/x-pack/plugins/apm/server/routes/observability_overview/route.ts b/x-pack/plugins/apm/server/routes/observability_overview/route.ts index 4ed5332801ef3..7128d664b0cef 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview/route.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview/route.ts @@ -15,6 +15,7 @@ import { rangeRt } from '../default_api_types'; import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { withApmSpan } from '../../utils/with_apm_span'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const observabilityOverviewHasDataRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/observability_overview/has_data', @@ -25,8 +26,11 @@ const observabilityOverviewHasDataRoute = createApmServerRoute({ hasData: boolean; indices: import('./../../../../observability/common/typings').ApmIndicesConfig; }> => { - const setup = await setupRequest(resources); - return await getHasData({ setup }); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); + return await getHasData({ indices: setup.indices, apmEventClient }); }, }); @@ -47,11 +51,14 @@ const observabilityOverviewRoute = createApmServerRoute({ | { value: undefined; timeseries: never[] } | { value: number; timeseries: Array<{ x: number; y: number | null }> }; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { bucketSize, intervalString, start, end } = resources.params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -71,13 +78,13 @@ const observabilityOverviewRoute = createApmServerRoute({ }> => { const [serviceCount, transactionPerMinute] = await Promise.all([ getServiceCount({ - setup, + apmEventClient, searchAggregatedTransactions, start, end, }), getTransactionsPerMinute({ - setup, + apmEventClient, bucketSize, searchAggregatedTransactions, start, diff --git a/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts b/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts index c820cdd8445fc..b261c2a4cfd4a 100644 --- a/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts +++ b/x-pack/plugins/apm/server/routes/service_groups/get_services_counts.ts @@ -8,23 +8,21 @@ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { Setup } from '../../lib/helpers/setup_request'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { SavedServiceGroup } from '../../../common/service_groups'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServicesCounts({ - setup, + apmEventClient, start, end, serviceGroups, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; serviceGroups: SavedServiceGroup[]; }) { - const { apmEventClient } = setup; - const serviceGroupsKueryMap: Record = serviceGroups.reduce((acc, sg) => { return { diff --git a/x-pack/plugins/apm/server/routes/service_groups/lookup_services.ts b/x-pack/plugins/apm/server/routes/service_groups/lookup_services.ts index 236b3e20222c2..4acfb49762629 100644 --- a/x-pack/plugins/apm/server/routes/service_groups/lookup_services.ts +++ b/x-pack/plugins/apm/server/routes/service_groups/lookup_services.ts @@ -13,23 +13,21 @@ import { SERVICE_ENVIRONMENT, SERVICE_NAME, } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function lookupServices({ - setup, + apmEventClient, kuery, start, end, maxNumberOfServices, }: { - setup: Setup; + apmEventClient: APMEventClient; kuery: string; start: number; end: number; maxNumberOfServices: number; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search('lookup_services', { apm: { events: [ diff --git a/x-pack/plugins/apm/server/routes/service_groups/route.ts b/x-pack/plugins/apm/server/routes/service_groups/route.ts index 4430aaae760eb..4da84e6848696 100644 --- a/x-pack/plugins/apm/server/routes/service_groups/route.ts +++ b/x-pack/plugins/apm/server/routes/service_groups/route.ts @@ -7,7 +7,6 @@ import * as t from 'io-ts'; import { apmServiceGroupMaxNumberOfServices } from '@kbn/observability-plugin/common'; -import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { kueryRt, rangeRt } from '../default_api_types'; import { getServiceGroups } from './get_service_groups'; @@ -17,6 +16,7 @@ import { deleteServiceGroup } from './delete_service_group'; import { lookupServices } from './lookup_services'; import { SavedServiceGroup } from '../../../common/service_groups'; import { getServicesCounts } from './get_services_counts'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const serviceGroupsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/service-groups', @@ -57,7 +57,7 @@ const serviceGroupsWithServiceCountRoute = createApmServerRoute({ query: { start, end }, } = params; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const serviceGroups = await getServiceGroups({ savedObjectsClient, @@ -65,7 +65,7 @@ const serviceGroupsWithServiceCountRoute = createApmServerRoute({ return { servicesCounts: await getServicesCounts({ - setup, + apmEventClient, serviceGroups, start, end, @@ -164,12 +164,12 @@ const serviceGroupServicesRoute = createApmServerRoute({ const { uiSettings: { client: uiSettingsClient }, } = await context.core; - const [setup, maxNumberOfServices] = await Promise.all([ - setupRequest(resources), + const [apmEventClient, maxNumberOfServices] = await Promise.all([ + getApmEventClient(resources), uiSettingsClient.get(apmServiceGroupMaxNumberOfServices), ]); const items = await lookupServices({ - setup, + apmEventClient, kuery, start, end, diff --git a/x-pack/plugins/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts b/x-pack/plugins/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts index e5d97708ae173..5d044846da6e7 100644 --- a/x-pack/plugins/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts +++ b/x-pack/plugins/apm/server/routes/service_map/fetch_service_paths_from_trace_ids.ts @@ -13,16 +13,14 @@ import { ExternalConnectionNode, ServiceConnectionNode, } from '../../../common/service_map'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function fetchServicePathsFromTraceIds( - setup: Setup, + apmEventClient: APMEventClient, traceIds: string[], start: number, end: number ) { - const { apmEventClient } = setup; - // make sure there's a range so ES can skip shards const dayInMs = 24 * 60 * 60 * 1000; const startRange = start - dayInMs; diff --git a/x-pack/plugins/apm/server/routes/service_map/get_service_map.ts b/x-pack/plugins/apm/server/routes/service_map/get_service_map.ts index 05424b4805d89..36a4218ae6e4a 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_service_map.ts @@ -28,9 +28,11 @@ import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; import { ServiceGroup } from '../../../common/service_groups'; import { serviceGroupQuery } from '../../lib/service_group_query'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export interface IEnvOptions { setup: Setup; + apmEventClient: APMEventClient; serviceNames?: string[]; environment: string; searchAggregatedTransactions: boolean; @@ -42,6 +44,7 @@ export interface IEnvOptions { async function getConnectionData({ setup, + apmEventClient, serviceNames, environment, start, @@ -50,7 +53,8 @@ async function getConnectionData({ }: IEnvOptions) { return withApmSpan('get_service_map_connections', async () => { const { traceIds } = await getTraceSampleIds({ - setup, + config: setup.config, + apmEventClient, serviceNames, environment, start, @@ -75,7 +79,7 @@ async function getConnectionData({ Promise.all( chunks.map((traceIdsChunk) => getServiceMapFromTraceIds({ - setup, + apmEventClient, traceIds: traceIdsChunk, start, end, @@ -100,7 +104,7 @@ async function getServicesData( ) { const { environment, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -146,8 +150,6 @@ async function getServicesData( }, }; - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_service_stats_for_service_map', params diff --git a/x-pack/plugins/apm/server/routes/service_map/get_service_map_dependency_node_info.ts b/x-pack/plugins/apm/server/routes/service_map/get_service_map_dependency_node_info.ts index 8d2210827918a..241672a7fa4db 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_service_map_dependency_node_info.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_service_map_dependency_node_info.ts @@ -17,14 +17,14 @@ import { EventOutcome } from '../../../common/event_outcome'; import { environmentQuery } from '../../../common/utils/environment_query'; import { withApmSpan } from '../../utils/with_apm_span'; import { calculateThroughputWithRange } from '../../lib/helpers/calculate_throughput'; -import { Setup } from '../../lib/helpers/setup_request'; import { getBucketSize } from '../../lib/helpers/get_bucket_size'; import { getFailedTransactionRateTimeSeries } from '../../lib/helpers/transaction_error_rate'; import { NodeStats } from '../../../common/service_map'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; interface Options { - setup: Setup; + apmEventClient: APMEventClient; environment: string; dependencyName: string; start: number; @@ -35,13 +35,12 @@ interface Options { export function getServiceMapDependencyNodeInfo({ environment, dependencyName, - setup, + apmEventClient, start, end, offset, }: Options): Promise { return withApmSpan('get_service_map_dependency_node_stats', async () => { - const { apmEventClient } = setup; const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/service_map/get_service_map_from_trace_ids.ts b/x-pack/plugins/apm/server/routes/service_map/get_service_map_from_trace_ids.ts index 6a61a514881b0..21daa774152f3 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_service_map_from_trace_ids.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_service_map_from_trace_ids.ts @@ -7,7 +7,7 @@ import { find, uniqBy } from 'lodash'; import { Connection, ConnectionNode } from '../../../common/service_map'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { fetchServicePathsFromTraceIds } from './fetch_service_paths_from_trace_ids'; export function getConnections({ @@ -40,18 +40,18 @@ export function getConnections({ } export async function getServiceMapFromTraceIds({ - setup, + apmEventClient, traceIds, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; traceIds: string[]; start: number; end: number; }) { const serviceMapFromTraceIdsScriptResponse = - await fetchServicePathsFromTraceIds(setup, traceIds, start, end); + await fetchServicePathsFromTraceIds(apmEventClient, traceIds, start, end); const serviceMapScriptedAggValue = serviceMapFromTraceIdsScriptResponse.aggregations?.service_map.value; diff --git a/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts index 72e8b3b267cf2..7f4b260b83309 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts @@ -24,7 +24,6 @@ import { import { environmentQuery } from '../../../common/utils/environment_query'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { getBucketSizeForAggregatedTransactions } from '../../lib/helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../../lib/helpers/setup_request'; import { getDocumentTypeFilterForTransactions, getDurationFieldForTransactions, @@ -36,9 +35,10 @@ import { percentCgroupMemoryUsedScript, percentSystemMemoryUsedScript, } from '../metrics/by_agent/shared/memory'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; interface Options { - setup: Setup; + apmEventClient: APMEventClient; environment: string; serviceName: string; searchAggregatedTransactions: boolean; @@ -53,7 +53,7 @@ interface TaskParameters { searchAggregatedTransactions: boolean; minutes: number; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; intervalString: string; @@ -65,7 +65,7 @@ interface TaskParameters { export function getServiceMapServiceNodeInfo({ environment, serviceName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -99,7 +99,7 @@ export function getServiceMapServiceNodeInfo({ searchAggregatedTransactions, minutes, serviceName, - setup, + apmEventClient, start: startWithOffset, end: endWithOffset, intervalString, @@ -125,7 +125,7 @@ export function getServiceMapServiceNodeInfo({ } async function getFailedTransactionsRateStats({ - setup, + apmEventClient, serviceName, environment, searchAggregatedTransactions, @@ -137,7 +137,7 @@ async function getFailedTransactionsRateStats({ return withApmSpan('get_error_rate_for_service_map_node', async () => { const { average, timeseries } = await getFailedTransactionRate({ environment, - setup, + apmEventClient, serviceName, searchAggregatedTransactions, start, @@ -154,7 +154,7 @@ async function getFailedTransactionsRateStats({ } async function getTransactionStats({ - setup, + apmEventClient, filter, minutes, searchAggregatedTransactions, @@ -163,8 +163,6 @@ async function getTransactionStats({ intervalString, offsetInMs, }: TaskParameters): Promise { - const { apmEventClient } = setup; - const durationField = getDurationFieldForTransactions( searchAggregatedTransactions ); @@ -241,15 +239,13 @@ async function getTransactionStats({ } async function getCpuStats({ - setup, + apmEventClient, filter, intervalString, start, end, offsetInMs, }: TaskParameters): Promise { - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_avg_cpu_usage_for_service_map_node', { @@ -295,7 +291,7 @@ async function getCpuStats({ } function getMemoryStats({ - setup, + apmEventClient, filter, intervalString, start, @@ -303,8 +299,6 @@ function getMemoryStats({ offsetInMs, }: TaskParameters) { return withApmSpan('get_memory_stats_for_service_map_node', async () => { - const { apmEventClient } = setup; - const getMemoryUsage = async ({ additionalFilters, script, diff --git a/x-pack/plugins/apm/server/routes/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/routes/service_map/get_trace_sample_ids.ts index 76d15d06da0b7..2289d3df26485 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_trace_sample_ids.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_trace_sample_ids.ts @@ -18,29 +18,30 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { SERVICE_MAP_TIMEOUT_ERROR } from '../../../common/service_map'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; import { serviceGroupQuery } from '../../lib/service_group_query'; import { ServiceGroup } from '../../../common/service_groups'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { APMConfig } from '../..'; const MAX_TRACES_TO_INSPECT = 1000; export async function getTraceSampleIds({ serviceNames, environment, - setup, + config, + apmEventClient, start, end, serviceGroup, }: { serviceNames?: string[]; environment: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; start: number; end: number; serviceGroup: ServiceGroup | null; }) { - const { apmEventClient, config } = setup; - const query = { bool: { filter: [...rangeQuery(start, end), ...serviceGroupQuery(serviceGroup)], diff --git a/x-pack/plugins/apm/server/routes/service_map/route.ts b/x-pack/plugins/apm/server/routes/service_map/route.ts index c2de9bf956a85..f833ce195c442 100644 --- a/x-pack/plugins/apm/server/routes/service_map/route.ts +++ b/x-pack/plugins/apm/server/routes/service_map/route.ts @@ -21,6 +21,7 @@ import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, rangeRt } from '../default_api_types'; import { getServiceGroup } from '../service_groups/get_service_group'; import { offsetRt } from '../../../common/comparison_rt'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const serviceMapRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/service-map', @@ -115,21 +116,23 @@ const serviceMapRoute = createApmServerRoute({ savedObjects: { client: savedObjectsClient }, uiSettings: { client: uiSettingsClient }, } = await context.core; - const [setup, serviceGroup, maxNumberOfServices] = await Promise.all([ - setupRequest(resources), - serviceGroupId - ? getServiceGroup({ - savedObjectsClient, - serviceGroupId, - }) - : Promise.resolve(null), - uiSettingsClient.get(apmServiceGroupMaxNumberOfServices), - ]); + const [setup, apmEventClient, serviceGroup, maxNumberOfServices] = + await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + serviceGroupId + ? getServiceGroup({ + savedObjectsClient, + serviceGroupId, + }) + : Promise.resolve(null), + uiSettingsClient.get(apmServiceGroupMaxNumberOfServices), + ]); const serviceNames = compact([serviceName]); const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -137,6 +140,7 @@ const serviceMapRoute = createApmServerRoute({ }); return getServiceMap({ setup, + apmEventClient, serviceNames, environment, searchAggregatedTransactions, @@ -176,7 +180,10 @@ const serviceMapServiceNodeRoute = createApmServerRoute({ if (!isActivePlatinumLicense(licensingContext.license)) { throw Boom.forbidden(invalidLicenseMessage); } - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { path: { serviceName }, @@ -184,7 +191,7 @@ const serviceMapServiceNodeRoute = createApmServerRoute({ } = params; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -193,7 +200,7 @@ const serviceMapServiceNodeRoute = createApmServerRoute({ const commonProps = { environment, - setup, + apmEventClient, serviceName, searchAggregatedTransactions, start, @@ -239,13 +246,19 @@ const serviceMapDependencyNodeRoute = createApmServerRoute({ if (!isActivePlatinumLicense(licensingContext.license)) { throw Boom.forbidden(invalidLicenseMessage); } - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { dependencyName, environment, start, end, offset }, } = params; - const commonProps = { environment, setup, dependencyName, start, end }; + const commonProps = { + environment, + apmEventClient, + dependencyName, + start, + end, + }; const [currentPeriod, previousPeriod] = await Promise.all([ getServiceMapDependencyNodeInfo(commonProps), diff --git a/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.test.ts b/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.test.ts index d763f1f913c4d..fef8a878f2e35 100644 --- a/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.test.ts +++ b/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.test.ts @@ -26,9 +26,9 @@ describe('getDerivedServiceAnnotations', () => { describe('with 0 versions', () => { it('returns no annotations', async () => { mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getDerivedServiceAnnotations({ - setup, + apmEventClient, serviceName: 'foo', environment: 'bar', searchAggregatedTransactions: false, @@ -54,9 +54,9 @@ describe('getDerivedServiceAnnotations', () => { describe('with 1 version', () => { it('returns no annotations', async () => { mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getDerivedServiceAnnotations({ - setup, + apmEventClient, serviceName: 'foo', environment: 'bar', searchAggregatedTransactions: false, @@ -87,9 +87,9 @@ describe('getDerivedServiceAnnotations', () => { versionsFirstSeen, ]; mock = await inspectSearchParams( - (setup) => + (setup, apmEventClient) => getDerivedServiceAnnotations({ - setup, + apmEventClient, serviceName: 'foo', environment: 'bar', searchAggregatedTransactions: false, diff --git a/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.ts b/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.ts index a718e9932661e..c98bc9dbeb17b 100644 --- a/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.ts +++ b/x-pack/plugins/apm/server/routes/services/annotations/get_derived_service_annotations.ts @@ -18,10 +18,10 @@ import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../../lib/helpers/transactions'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getDerivedServiceAnnotations({ - setup, + apmEventClient, serviceName, environment, searchAggregatedTransactions, @@ -30,13 +30,11 @@ export async function getDerivedServiceAnnotations({ }: { serviceName: string; environment: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; }) { - const { apmEventClient } = setup; - const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, ...getDocumentTypeFilterForTransactions(searchAggregatedTransactions), diff --git a/x-pack/plugins/apm/server/routes/services/annotations/index.test.ts b/x-pack/plugins/apm/server/routes/services/annotations/index.test.ts index a044648bd7a4f..729a2c16dd65e 100644 --- a/x-pack/plugins/apm/server/routes/services/annotations/index.test.ts +++ b/x-pack/plugins/apm/server/routes/services/annotations/index.test.ts @@ -10,11 +10,11 @@ import { } from '@kbn/observability-plugin/server'; import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { getServiceAnnotations } from '.'; -import { Setup } from '../../../lib/helpers/setup_request'; import * as GetDerivedServiceAnnotations from './get_derived_service_annotations'; import * as GetStoredAnnotations from './get_stored_annotations'; import { Annotation, AnnotationType } from '../../../../common/annotations'; import { errors } from '@elastic/elasticsearch'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; describe('getServiceAnnotations', () => { const storedAnnotations = [ @@ -60,7 +60,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }); expect(annotations).toEqual({ annotations: storedAnnotations, @@ -96,7 +96,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }); expect(annotations).toEqual({ annotations: storedAnnotations, @@ -133,7 +133,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }) ).rejects.toThrow('BOOM'); }); @@ -171,7 +171,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }); expect(annotations).toEqual({ annotations: [] }); }); @@ -206,7 +206,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }) ).rejects.toThrow('BOOM'); }); @@ -240,7 +240,7 @@ describe('getServiceAnnotations', () => { client: {} as ElasticsearchClient, logger: {} as Logger, annotationsClient: {} as ScopedAnnotationsClient, - setup: {} as Setup, + apmEventClient: {} as APMEventClient, }); expect(annotations).toEqual({ annotations: storedAnnotations, diff --git a/x-pack/plugins/apm/server/routes/services/annotations/index.ts b/x-pack/plugins/apm/server/routes/services/annotations/index.ts index cc103f2fe6cef..41f199f456d7d 100644 --- a/x-pack/plugins/apm/server/routes/services/annotations/index.ts +++ b/x-pack/plugins/apm/server/routes/services/annotations/index.ts @@ -7,12 +7,12 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { ScopedAnnotationsClient } from '@kbn/observability-plugin/server'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { getDerivedServiceAnnotations } from './get_derived_service_annotations'; import { getStoredAnnotations } from './get_stored_annotations'; export async function getServiceAnnotations({ - setup, + apmEventClient, searchAggregatedTransactions, serviceName, environment, @@ -24,7 +24,7 @@ export async function getServiceAnnotations({ }: { serviceName: string; environment: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; annotationsClient?: ScopedAnnotationsClient; client: ElasticsearchClient; @@ -38,7 +38,7 @@ export async function getServiceAnnotations({ // start fetching derived annotations (based on transactions), but don't wait on it // it will likely be significantly slower than the stored annotations const derivedAnnotationsPromise = getDerivedServiceAnnotations({ - setup, + apmEventClient, serviceName, environment, searchAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_agent.ts b/x-pack/plugins/apm/server/routes/services/get_service_agent.ts index 78b68f3a88ba4..e848dd9befbfb 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_agent.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_agent.ts @@ -12,7 +12,7 @@ import { SERVICE_NAME, SERVICE_RUNTIME_NAME, } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; interface ServiceAgent { agent?: { @@ -27,17 +27,15 @@ interface ServiceAgent { export async function getServiceAgent({ serviceName, - setup, + apmEventClient, start, end, }: { serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { terminate_after: 1, apm: { diff --git a/x-pack/plugins/apm/server/routes/services/get_service_dependencies.ts b/x-pack/plugins/apm/server/routes/services/get_service_dependencies.ts index cf60502e9861b..e0878a9361f30 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_dependencies.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_dependencies.ts @@ -9,10 +9,10 @@ import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; import { getConnectionStats } from '../../lib/connections/get_connection_stats'; import { getConnectionStatsItemsWithRelativeImpact } from '../../lib/connections/get_connection_stats/get_connection_stats_items_with_relative_impact'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceDependencies({ - setup, + apmEventClient, start, end, serviceName, @@ -20,7 +20,7 @@ export async function getServiceDependencies({ environment, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; serviceName: string; @@ -29,7 +29,7 @@ export async function getServiceDependencies({ offset?: string; }) { const statsItems = await getConnectionStats({ - setup, + apmEventClient, start, end, numBuckets, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_dependencies_breakdown.ts b/x-pack/plugins/apm/server/routes/services/get_service_dependencies_breakdown.ts index 4fc7667fa2349..8b940b8525b66 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_dependencies_breakdown.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_dependencies_breakdown.ts @@ -9,18 +9,18 @@ import { kqlQuery } from '@kbn/observability-plugin/server'; import { getNodeName } from '../../../common/connections'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../common/utils/environment_query'; -import { Setup } from '../../lib/helpers/setup_request'; import { getConnectionStats } from '../../lib/connections/get_connection_stats'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceDependenciesBreakdown({ - setup, + apmEventClient, start, end, serviceName, environment, kuery, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; serviceName: string; @@ -28,7 +28,7 @@ export async function getServiceDependenciesBreakdown({ kuery: string; }) { const items = await getConnectionStats({ - setup, + apmEventClient, start, end, numBuckets: 100, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instance_metadata_details.ts b/x-pack/plugins/apm/server/routes/services/get_service_instance_metadata_details.ts index c69ea36d3f236..22893a5acefd3 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instance_metadata_details.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instance_metadata_details.ts @@ -12,27 +12,26 @@ import { SERVICE_NAME, SERVICE_NODE_NAME, } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; import { maybe } from '../../../common/utils/maybe'; import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../lib/helpers/transactions'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceInstanceMetadataDetails({ serviceName, serviceNodeName, - setup, + apmEventClient, start, end, }: { serviceName: string; serviceNodeName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { - const { apmEventClient } = setup; const filter = [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [SERVICE_NODE_NAME]: serviceNodeName } }, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instances/detailed_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_service_instances/detailed_statistics.ts index 01e82c6d57c77..2d23345c3ba1b 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instances/detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instances/detailed_statistics.ts @@ -11,15 +11,15 @@ import { Coordinate } from '../../../../typings/timeseries'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { joinByKey } from '../../../../common/utils/join_by_key'; import { withApmSpan } from '../../../utils/with_apm_span'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getServiceInstancesSystemMetricStatistics } from './get_service_instances_system_metric_statistics'; import { getServiceInstancesTransactionStatistics } from './get_service_instances_transaction_statistics'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; interface ServiceInstanceDetailedStatisticsParams { environment: string; kuery: string; latencyAggregationType: LatencyAggregationType; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; transactionType: string; searchAggregatedTransactions: boolean; @@ -67,7 +67,7 @@ export async function getServiceInstancesDetailedStatisticsPeriods({ environment, kuery, latencyAggregationType, - setup, + apmEventClient, serviceName, transactionType, searchAggregatedTransactions, @@ -80,7 +80,7 @@ export async function getServiceInstancesDetailedStatisticsPeriods({ environment: string; kuery: string; latencyAggregationType: LatencyAggregationType; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; transactionType: string; searchAggregatedTransactions: boolean; @@ -97,7 +97,7 @@ export async function getServiceInstancesDetailedStatisticsPeriods({ environment, kuery, latencyAggregationType, - setup, + apmEventClient, serviceName, transactionType, searchAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts index 7e899f4140c15..71e1b2f7658c1 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_system_metric_statistics.ts @@ -20,7 +20,7 @@ import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; import { Coordinate } from '../../../../typings/timeseries'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { percentCgroupMemoryUsedScript, percentSystemMemoryUsedScript, @@ -48,7 +48,7 @@ export async function getServiceInstancesSystemMetricStatistics< >({ environment, kuery, - setup, + apmEventClient, serviceName, size, start, @@ -58,7 +58,7 @@ export async function getServiceInstancesSystemMetricStatistics< isComparisonSearch, offset, }: { - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; start: number; end: number; @@ -70,8 +70,6 @@ export async function getServiceInstancesSystemMetricStatistics< isComparisonSearch: T; offset?: string; }): Promise>> { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_transaction_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_transaction_statistics.ts index 464b4c07ec58d..8bff6b47308bc 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_transaction_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instances/get_service_instances_transaction_statistics.ts @@ -27,8 +27,8 @@ import { getLatencyAggregation, getLatencyValue, } from '../../../lib/helpers/latency_aggregation_type'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; interface ServiceInstanceTransactionPrimaryStatistics { serviceNodeName: string; @@ -54,7 +54,7 @@ export async function getServiceInstancesTransactionStatistics< environment, kuery, latencyAggregationType, - setup, + apmEventClient, transactionType, serviceName, size, @@ -67,7 +67,7 @@ export async function getServiceInstancesTransactionStatistics< offset, }: { latencyAggregationType: LatencyAggregationType; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; transactionType: string; searchAggregatedTransactions: boolean; @@ -81,8 +81,6 @@ export async function getServiceInstancesTransactionStatistics< numBuckets?: number; offset?: string; }): Promise>> { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_instances/main_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_service_instances/main_statistics.ts index a09b105cb5a79..095f3c981a689 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_instances/main_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_instances/main_statistics.ts @@ -8,7 +8,7 @@ import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { joinByKey } from '../../../../common/utils/join_by_key'; import { withApmSpan } from '../../../utils/with_apm_span'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { getServiceInstancesSystemMetricStatistics } from './get_service_instances_system_metric_statistics'; import { getServiceInstancesTransactionStatistics } from './get_service_instances_transaction_statistics'; @@ -16,7 +16,7 @@ interface ServiceInstanceMainStatisticsParams { environment: string; kuery: string; latencyAggregationType: LatencyAggregationType; - setup: Setup; + apmEventClient: APMEventClient; serviceName: string; transactionType: string; searchAggregatedTransactions: boolean; diff --git a/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts b/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts index a7014d65b7182..40e48101784c4 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_metadata_details.ts @@ -28,7 +28,7 @@ import { import { ContainerType } from '../../../common/service_metadata'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { should } from './get_service_metadata_icons'; type ServiceMetadataDetailsRaw = Pick< @@ -78,19 +78,17 @@ export interface ServiceMetadataDetails { export async function getServiceMetadataDetails({ serviceName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, }: { serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; }): Promise { - const { apmEventClient } = setup; - const filter = [ { term: { [SERVICE_NAME]: serviceName } }, ...rangeQuery(start, end), diff --git a/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts b/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts index 7cf5304ded844..6b6ee0489ad19 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_metadata_icons.ts @@ -20,7 +20,7 @@ import { import { ContainerType } from '../../../common/service_metadata'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; type ServiceMetadataIconsRaw = Pick< TransactionRaw, @@ -44,19 +44,17 @@ export const should = [ export async function getServiceMetadataIcons({ serviceName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, }: { serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; }): Promise { - const { apmEventClient } = setup; - const filter = [ { term: { [SERVICE_NAME]: serviceName } }, ...rangeQuery(start, end), diff --git a/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts b/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts index 7a8e786c2912b..227dcab3deb41 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_node_metadata.ts @@ -7,7 +7,6 @@ import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../lib/helpers/setup_request'; import { HOST_NAME, CONTAINER_ID, @@ -22,24 +21,23 @@ import { serviceNodeNameQuery, } from '../../../common/utils/environment_query'; import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceNodeMetadata({ kuery, serviceName, serviceNodeName, - setup, + apmEventClient, start, end, }: { kuery: string; serviceName: string; serviceNodeName: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { apm: { events: [ProcessorEvent.metric], diff --git a/x-pack/plugins/apm/server/routes/services/get_service_transaction_group_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_service_transaction_group_detailed_statistics.ts index df02ba0647483..5e8c5b0cc1c55 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_transaction_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_transaction_group_detailed_statistics.ts @@ -28,16 +28,16 @@ import { getLatencyAggregation, getLatencyValue, } from '../../lib/helpers/latency_aggregation_type'; -import { Setup } from '../../lib/helpers/setup_request'; import { calculateFailedTransactionRate } from '../../lib/helpers/transaction_error_rate'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceTransactionGroupDetailedStatistics({ environment, kuery, serviceName, transactionNames, - setup, + apmEventClient, numBuckets, searchAggregatedTransactions, transactionType, @@ -50,7 +50,7 @@ export async function getServiceTransactionGroupDetailedStatistics({ kuery: string; serviceName: string; transactionNames: string[]; - setup: Setup; + apmEventClient: APMEventClient; numBuckets: number; searchAggregatedTransactions: boolean; transactionType: string; @@ -67,8 +67,6 @@ export async function getServiceTransactionGroupDetailedStatistics({ impact: number; }> > { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -185,7 +183,7 @@ export async function getServiceTransactionGroupDetailedStatistics({ export async function getServiceTransactionGroupDetailedStatisticsPeriods({ serviceName, transactionNames, - setup, + apmEventClient, numBuckets, searchAggregatedTransactions, transactionType, @@ -198,7 +196,7 @@ export async function getServiceTransactionGroupDetailedStatisticsPeriods({ }: { serviceName: string; transactionNames: string[]; - setup: Setup; + apmEventClient: APMEventClient; numBuckets: number; searchAggregatedTransactions: boolean; transactionType: string; @@ -210,7 +208,7 @@ export async function getServiceTransactionGroupDetailedStatisticsPeriods({ offset?: string; }) { const commonProps = { - setup, + apmEventClient, serviceName, transactionNames, searchAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_transaction_groups.ts b/x-pack/plugins/apm/server/routes/services/get_service_transaction_groups.ts index 4a589f96b68f1..7b828d91af9a0 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_transaction_groups.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_transaction_groups.ts @@ -25,8 +25,9 @@ import { getLatencyAggregation, getLatencyValue, } from '../../lib/helpers/latency_aggregation_type'; -import { Setup } from '../../lib/helpers/setup_request'; import { calculateFailedTransactionRate } from '../../lib/helpers/transaction_error_rate'; +import { APMConfig } from '../..'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export type ServiceOverviewTransactionGroupSortField = | 'name' @@ -39,7 +40,8 @@ export async function getServiceTransactionGroups({ environment, kuery, serviceName, - setup, + config, + apmEventClient, searchAggregatedTransactions, transactionType, latencyAggregationType, @@ -49,14 +51,14 @@ export async function getServiceTransactionGroups({ environment: string; kuery: string; serviceName: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; transactionType: string; latencyAggregationType: LatencyAggregationType; start: number; end: number; }) { - const { apmEventClient, config } = setup; const bucketSize = config.ui.transactionGroupBucketSize; const field = getDurationFieldForTransactions(searchAggregatedTransactions); diff --git a/x-pack/plugins/apm/server/routes/services/get_service_transaction_types.ts b/x-pack/plugins/apm/server/routes/services/get_service_transaction_types.ts index 73d53a24d4f24..1081c35b7eb70 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_transaction_types.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_transaction_types.ts @@ -10,27 +10,25 @@ import { SERVICE_NAME, TRANSACTION_TYPE, } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../lib/helpers/transactions'; export async function getServiceTransactionTypes({ - setup, + apmEventClient, serviceName, searchAggregatedTransactions, start, end, }: { serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; }) { - const { apmEventClient } = setup; - const params = { apm: { events: [getProcessorEventForTransactions(searchAggregatedTransactions)], diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_service_aggregated_transaction_stats.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_service_aggregated_transaction_stats.ts index d7282dfd2db85..73436ad9afb67 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_service_aggregated_transaction_stats.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_service_aggregated_transaction_stats.ts @@ -24,15 +24,15 @@ import { environmentQuery } from '../../../../common/utils/environment_query'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; import { calculateThroughputWithRange } from '../../../lib/helpers/calculate_throughput'; import { calculateFailedTransactionRateFromServiceMetrics } from '../../../lib/helpers/transaction_error_rate'; -import { ServicesItemsSetup } from './get_services_items'; import { serviceGroupQuery } from '../../../lib/service_group_query'; import { ServiceGroup } from '../../../../common/service_groups'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; import { getDocumentTypeFilterForServiceMetrics } from '../../../lib/helpers/service_metrics'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; interface AggregationParams { environment: string; kuery: string; - setup: ServicesItemsSetup; + apmEventClient: APMEventClient; maxNumServices: number; start: number; end: number; @@ -43,15 +43,13 @@ interface AggregationParams { export async function getServiceAggregatedTransactionStats({ environment, kuery, - setup, + apmEventClient, maxNumServices, start, end, serviceGroup, randomSampler, }: AggregationParams) { - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_service_aggregated_transaction_stats', { diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_service_transaction_stats.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_service_transaction_stats.ts index 0d0d831b61a1e..ba5903133e445 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_service_transaction_stats.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_service_transaction_stats.ts @@ -28,15 +28,15 @@ import { calculateFailedTransactionRate, getOutcomeAggregation, } from '../../../lib/helpers/transaction_error_rate'; -import { ServicesItemsSetup } from './get_services_items'; import { serviceGroupQuery } from '../../../lib/service_group_query'; import { ServiceGroup } from '../../../../common/service_groups'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; interface AggregationParams { environment: string; kuery: string; - setup: ServicesItemsSetup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; maxNumServices: number; start: number; @@ -48,7 +48,7 @@ interface AggregationParams { export async function getServiceTransactionStats({ environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, maxNumServices, start, @@ -56,8 +56,6 @@ export async function getServiceTransactionStats({ serviceGroup, randomSampler, }: AggregationParams) { - const { apmEventClient } = setup; - const outcomes = getOutcomeAggregation(); const metrics = { diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_services_from_error_and_metric_documents.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_services_from_error_and_metric_documents.ts index 92cb6396856d8..7ae1698b988dd 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_services_from_error_and_metric_documents.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_services_from_error_and_metric_documents.ts @@ -14,14 +14,14 @@ import { SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; -import { Setup } from '../../../lib/helpers/setup_request'; import { serviceGroupQuery } from '../../../lib/service_group_query'; import { ServiceGroup } from '../../../../common/service_groups'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServicesFromErrorAndMetricDocuments({ environment, - setup, + apmEventClient, maxNumServices, kuery, start, @@ -29,7 +29,7 @@ export async function getServicesFromErrorAndMetricDocuments({ serviceGroup, randomSampler, }: { - setup: Setup; + apmEventClient: APMEventClient; environment: string; maxNumServices: number; kuery: string; @@ -38,8 +38,6 @@ export async function getServicesFromErrorAndMetricDocuments({ serviceGroup: ServiceGroup | null; randomSampler: RandomSampler; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_services_from_error_and_metric_documents', { diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts index 7ee41a2bea0e0..e3d2b72ec56b2 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts @@ -15,6 +15,7 @@ import { getServiceAggregatedTransactionStats } from './get_service_aggregated_t import { mergeServiceStats } from './merge_service_stats'; import { ServiceGroup } from '../../../../common/service_groups'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export type ServicesItemsSetup = Setup; @@ -24,6 +25,7 @@ export async function getServicesItems({ environment, kuery, setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, logger, @@ -35,6 +37,7 @@ export async function getServicesItems({ environment: string; kuery: string; setup: ServicesItemsSetup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; searchAggregatedServiceMetrics: boolean; logger: Logger; @@ -44,10 +47,9 @@ export async function getServicesItems({ randomSampler: RandomSampler; }) { return withApmSpan('get_services_items', async () => { - const params = { + const commonParams = { environment, kuery, - setup, searchAggregatedTransactions, searchAggregatedServiceMetrics, maxNumServices: MAX_NUMBER_OF_SERVICES, @@ -63,10 +65,19 @@ export async function getServicesItems({ healthStatuses, ] = await Promise.all([ searchAggregatedServiceMetrics - ? getServiceAggregatedTransactionStats(params) - : getServiceTransactionStats(params), - getServicesFromErrorAndMetricDocuments(params), - getHealthStatuses(params).catch((err) => { + ? getServiceAggregatedTransactionStats({ + ...commonParams, + apmEventClient, + }) + : getServiceTransactionStats({ + ...commonParams, + apmEventClient, + }), + getServicesFromErrorAndMetricDocuments({ + ...commonParams, + apmEventClient, + }), + getHealthStatuses({ ...commonParams, setup }).catch((err) => { logger.error(err); return []; }), diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_sorted_and_filtered_services.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_sorted_and_filtered_services.ts index a006c5b02509d..62748c722ffee 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/get_sorted_and_filtered_services.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/get_sorted_and_filtered_services.ts @@ -7,6 +7,7 @@ import { Logger } from '@kbn/logging'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { Environment } from '../../../../common/environment_rt'; @@ -16,8 +17,42 @@ import { Setup } from '../../../lib/helpers/setup_request'; import { getHealthStatuses } from './get_health_statuses'; import { lookupServices } from '../../service_groups/lookup_services'; +export async function getServiceNamesFromTermsEnum({ + apmEventClient, + environment, + maxNumberOfServices, +}: { + apmEventClient: APMEventClient; + environment: Environment; + maxNumberOfServices: number; +}) { + if (environment !== ENVIRONMENT_ALL.value) { + return []; + } + const response = await apmEventClient.termsEnum( + 'get_services_from_terms_enum', + { + apm: { + events: [ + ProcessorEvent.transaction, + ProcessorEvent.span, + ProcessorEvent.metric, + ProcessorEvent.error, + ], + }, + body: { + size: maxNumberOfServices, + field: SERVICE_NAME, + }, + } + ); + + return response.terms; +} + export async function getSortedAndFilteredServices({ setup, + apmEventClient, start, end, environment, @@ -26,6 +61,7 @@ export async function getSortedAndFilteredServices({ maxNumberOfServices, }: { setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: Environment; @@ -33,33 +69,6 @@ export async function getSortedAndFilteredServices({ serviceGroup: ServiceGroup | null; maxNumberOfServices: number; }) { - const { apmEventClient } = setup; - - async function getServiceNamesFromTermsEnum() { - if (environment !== ENVIRONMENT_ALL.value) { - return []; - } - const response = await apmEventClient.termsEnum( - 'get_services_from_terms_enum', - { - apm: { - events: [ - ProcessorEvent.transaction, - ProcessorEvent.span, - ProcessorEvent.metric, - ProcessorEvent.error, - ], - }, - body: { - size: maxNumberOfServices, - field: SERVICE_NAME, - }, - } - ); - - return response.terms; - } - const [servicesWithHealthStatuses, selectedServices] = await Promise.all([ getHealthStatuses({ setup, @@ -72,13 +81,17 @@ export async function getSortedAndFilteredServices({ }), serviceGroup ? getServiceNamesFromServiceGroup({ - setup, + apmEventClient, start, end, maxNumberOfServices, serviceGroup, }) - : getServiceNamesFromTermsEnum(), + : getServiceNamesFromTermsEnum({ + apmEventClient, + environment, + maxNumberOfServices, + }), ]); const services = joinByKey( @@ -93,20 +106,20 @@ export async function getSortedAndFilteredServices({ } async function getServiceNamesFromServiceGroup({ - setup, + apmEventClient, start, end, maxNumberOfServices, serviceGroup: { kuery }, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; maxNumberOfServices: number; serviceGroup: ServiceGroup; }) { const services = await lookupServices({ - setup, + apmEventClient, kuery, start, end, diff --git a/x-pack/plugins/apm/server/routes/services/get_services/index.ts b/x-pack/plugins/apm/server/routes/services/get_services/index.ts index 83932f630357e..d9eca887c8e08 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services/index.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services/index.ts @@ -11,11 +11,13 @@ import { Setup } from '../../../lib/helpers/setup_request'; import { getServicesItems } from './get_services_items'; import { ServiceGroup } from '../../../../common/service_groups'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServices({ environment, kuery, setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, logger, @@ -27,6 +29,7 @@ export async function getServices({ environment: string; kuery: string; setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; searchAggregatedServiceMetrics: boolean; logger: Logger; @@ -40,6 +43,7 @@ export async function getServices({ environment, kuery, setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, logger, diff --git a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_aggregated_transaction_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_aggregated_transaction_detailed_statistics.ts index 83df6d0773d73..f6ae0b220ee43 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_aggregated_transaction_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_aggregated_transaction_detailed_statistics.ts @@ -24,16 +24,16 @@ import { environmentQuery } from '../../../../common/utils/environment_query'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; import { calculateThroughputWithRange } from '../../../lib/helpers/calculate_throughput'; import { getBucketSizeForAggregatedTransactions } from '../../../lib/helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../../../lib/helpers/setup_request'; import { calculateFailedTransactionRateFromServiceMetrics } from '../../../lib/helpers/transaction_error_rate'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; import { getDocumentTypeFilterForServiceMetrics } from '../../../lib/helpers/service_metrics'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceAggregatedTransactionDetailedStats({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedServiceMetrics, offset, start, @@ -43,14 +43,13 @@ export async function getServiceAggregatedTransactionDetailedStats({ serviceNames: string[]; environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedServiceMetrics: boolean; offset?: string; start: number; end: number; randomSampler: RandomSampler; }) { - const { apmEventClient } = setup; const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -185,7 +184,7 @@ export async function getServiceAggregatedDetailedStatsPeriods({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedServiceMetrics, offset, start, @@ -195,7 +194,7 @@ export async function getServiceAggregatedDetailedStatsPeriods({ serviceNames: string[]; environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedServiceMetrics: boolean; offset?: string; start: number; @@ -207,7 +206,7 @@ export async function getServiceAggregatedDetailedStatsPeriods({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedServiceMetrics, start, end, diff --git a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts index c587b3711e58a..fbddad6aa1c42 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/get_service_transaction_detailed_statistics.ts @@ -25,18 +25,18 @@ import { } from '../../../lib/helpers/transactions'; import { calculateThroughputWithRange } from '../../../lib/helpers/calculate_throughput'; import { getBucketSizeForAggregatedTransactions } from '../../../lib/helpers/get_bucket_size_for_aggregated_transactions'; -import { Setup } from '../../../lib/helpers/setup_request'; import { calculateFailedTransactionRate, getOutcomeAggregation, } from '../../../lib/helpers/transaction_error_rate'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServiceTransactionDetailedStats({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, offset, start, @@ -46,14 +46,13 @@ export async function getServiceTransactionDetailedStats({ serviceNames: string[]; environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; offset?: string; start: number; end: number; randomSampler: RandomSampler; }) { - const { apmEventClient } = setup; const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -182,7 +181,7 @@ export async function getServiceDetailedStatsPeriods({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, offset, start, @@ -192,7 +191,7 @@ export async function getServiceDetailedStatsPeriods({ serviceNames: string[]; environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; offset?: string; start: number; @@ -204,7 +203,7 @@ export async function getServiceDetailedStatsPeriods({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, start, end, diff --git a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts index b142b3484d559..583bb6e938aad 100644 --- a/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts +++ b/x-pack/plugins/apm/server/routes/services/get_services_detailed_statistics/index.ts @@ -5,16 +5,16 @@ * 2.0. */ -import { Setup } from '../../../lib/helpers/setup_request'; import { getServiceDetailedStatsPeriods } from './get_service_transaction_detailed_statistics'; import { getServiceAggregatedDetailedStatsPeriods } from './get_service_aggregated_transaction_detailed_statistics'; import { RandomSampler } from '../../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getServicesDetailedStatistics({ serviceNames, environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, offset, @@ -25,7 +25,7 @@ export async function getServicesDetailedStatistics({ serviceNames: string[]; environment: string; kuery: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; searchAggregatedServiceMetrics: boolean; offset?: string; @@ -37,7 +37,7 @@ export async function getServicesDetailedStatistics({ serviceNames, environment, kuery, - setup, + apmEventClient, start, end, randomSampler, diff --git a/x-pack/plugins/apm/server/routes/services/get_throughput.ts b/x-pack/plugins/apm/server/routes/services/get_throughput.ts index a4312b6dca896..5413bcbf56322 100644 --- a/x-pack/plugins/apm/server/routes/services/get_throughput.ts +++ b/x-pack/plugins/apm/server/routes/services/get_throughput.ts @@ -20,16 +20,16 @@ import { getDocumentTypeFilterForTransactions, getProcessorEventForTransactions, } from '../../lib/helpers/transactions'; -import { Setup } from '../../lib/helpers/setup_request'; import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { getBucketSizeForAggregatedTransactions } from '../../lib/helpers/get_bucket_size_for_aggregated_transactions'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; interface Options { environment: string; kuery: string; searchAggregatedTransactions: boolean; serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; transactionType: string; transactionName?: string; start: number; @@ -42,15 +42,13 @@ export async function getThroughput({ kuery, searchAggregatedTransactions, serviceName, - setup, + apmEventClient, transactionType, transactionName, start, end, offset, }: Options) { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, diff --git a/x-pack/plugins/apm/server/routes/services/queries.test.ts b/x-pack/plugins/apm/server/routes/services/queries.test.ts index ff9f79867c2d7..1410772704648 100644 --- a/x-pack/plugins/apm/server/routes/services/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/services/queries.test.ts @@ -23,10 +23,10 @@ describe('services queries', () => { }); it('fetches the service agent name', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getServiceAgent({ serviceName: 'foo', - setup, + apmEventClient, start: 0, end: 50000, }) @@ -36,10 +36,10 @@ describe('services queries', () => { }); it('fetches the service transaction types', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getServiceTransactionTypes({ serviceName: 'foo', - setup, + apmEventClient, searchAggregatedTransactions: false, start: 0, end: 50000, @@ -50,9 +50,10 @@ describe('services queries', () => { }); it('fetches the service items', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getServicesItems({ setup, + apmEventClient, searchAggregatedTransactions: false, searchAggregatedServiceMetrics: false, logger: {} as any, @@ -74,7 +75,9 @@ describe('services queries', () => { }); it('fetches the agent status', async () => { - mock = await inspectSearchParams((setup) => hasHistoricalAgentData(setup)); + mock = await inspectSearchParams((setup, apmEventClient) => + hasHistoricalAgentData(apmEventClient) + ); expect(mock.params).toMatchSnapshot(); }); diff --git a/x-pack/plugins/apm/server/routes/services/route.ts b/x-pack/plugins/apm/server/routes/services/route.ts index 1f1cac260331f..0dc2de2d68b45 100644 --- a/x-pack/plugins/apm/server/routes/services/route.ts +++ b/x-pack/plugins/apm/server/routes/services/route.ts @@ -56,6 +56,7 @@ import { getServiceGroup } from '../service_groups/get_service_group'; import { offsetRt } from '../../../common/comparison_rt'; import { getRandomSampler } from '../../lib/helpers/get_random_sampler'; import { createInfraMetricsClient } from '../../lib/helpers/create_es_client/create_infra_metrics_client/create_infra_metrics_client'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const servicesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services', @@ -126,15 +127,17 @@ const servicesRoute = createApmServerRoute({ const savedObjectsClient = (await context.core).savedObjects.client; const coreContext = await resources.context.core; - const [setup, serviceGroup, randomSampler] = await Promise.all([ - setupRequest(resources), - serviceGroupId - ? getServiceGroup({ savedObjectsClient, serviceGroupId }) - : Promise.resolve(null), - getRandomSampler({ security, request, probability }), - ]); + const [setup, apmEventClient, serviceGroup, randomSampler] = + await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + serviceGroupId + ? getServiceGroup({ savedObjectsClient, serviceGroupId }) + : Promise.resolve(null), + getRandomSampler({ security, request, probability }), + ]); - const { apmEventClient, config } = setup; + const { config } = setup; const serviceMetricsEnabled = await coreContext.uiSettings.client.get(enableServiceMetrics); @@ -153,6 +156,7 @@ const servicesRoute = createApmServerRoute({ environment, kuery, setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, logger, @@ -223,12 +227,13 @@ const servicesDetailedStatisticsRoute = createApmServerRoute({ const { serviceNames } = params.body; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); - const { apmEventClient, config } = setup; + const { config } = setup; const serviceMetricsEnabled = await coreContext.uiSettings.client.get(enableServiceMetrics); @@ -250,7 +255,7 @@ const servicesDetailedStatisticsRoute = createApmServerRoute({ return getServicesDetailedStatistics({ environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, searchAggregatedServiceMetrics, offset, @@ -274,14 +279,17 @@ const serviceMetadataDetailsRoute = createApmServerRoute({ ): Promise< import('./get_service_metadata_details').ServiceMetadataDetails > => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const infraMetricsClient = createInfraMetricsClient(resources); const { params } = resources; const { serviceName } = params.path; const { start, end } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -290,7 +298,7 @@ const serviceMetadataDetailsRoute = createApmServerRoute({ const serviceMetadataDetails = await getServiceMetadataDetails({ serviceName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -321,13 +329,16 @@ const serviceMetadataIconsRoute = createApmServerRoute({ handler: async ( resources ): Promise => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; const { start, end } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -336,7 +347,7 @@ const serviceMetadataIconsRoute = createApmServerRoute({ return getServiceMetadataIcons({ serviceName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -359,14 +370,14 @@ const serviceAgentRoute = createApmServerRoute({ | { agentName?: undefined; runtimeName?: undefined } | { agentName: string | undefined; runtimeName: string | undefined } > => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { start, end } = params.query; return getServiceAgent({ serviceName, - setup, + apmEventClient, start, end, }); @@ -383,16 +394,19 @@ const serviceTransactionTypesRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ transactionTypes: string[] }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; const { start, end } = params.query; return getServiceTransactionTypes({ serviceName, - setup, + apmEventClient, searchAggregatedTransactions: await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -418,14 +432,14 @@ const serviceNodeMetadataRoute = createApmServerRoute({ handler: async ( resources ): Promise<{ host: string | number; containerId: string | number }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName, serviceNodeName } = params.path; const { kuery, start, end } = params.query; return getServiceNodeMetadata({ kuery, - setup, + apmEventClient, serviceName, serviceNodeName, start, @@ -448,7 +462,10 @@ const serviceAnnotationsRoute = createApmServerRoute({ ): Promise<{ annotations: Array; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params, plugins, context, request, logger } = resources; const { serviceName } = params.path; const { environment, start, end } = params.query; @@ -466,7 +483,7 @@ const serviceAnnotationsRoute = createApmServerRoute({ ) : undefined, getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, start, end, @@ -477,7 +494,7 @@ const serviceAnnotationsRoute = createApmServerRoute({ return getServiceAnnotations({ environment, - setup, + apmEventClient, searchAggregatedTransactions, serviceName, annotationsClient, @@ -586,7 +603,10 @@ const serviceThroughputRoute = createApmServerRoute({ y: import('./../../../typings/common').Maybe; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; const { @@ -599,7 +619,8 @@ const serviceThroughputRoute = createApmServerRoute({ end, } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -610,7 +631,7 @@ const serviceThroughputRoute = createApmServerRoute({ kuery, searchAggregatedTransactions, serviceName, - setup, + apmEventClient, transactionType, transactionName, }; @@ -680,7 +701,10 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({ memoryUsage?: number | null | undefined; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; const { @@ -694,7 +718,8 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({ } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -706,7 +731,7 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({ kuery, latencyAggregationType, serviceName, - setup, + apmEventClient, transactionType, searchAggregatedTransactions, start, @@ -719,7 +744,7 @@ const serviceInstancesMainStatisticsRoute = createApmServerRoute({ kuery, latencyAggregationType, serviceName, - setup, + apmEventClient, transactionType, searchAggregatedTransactions, start, @@ -800,7 +825,10 @@ const serviceInstancesDetailedStatisticsRoute = createApmServerRoute({ serviceNodeName: string; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; const { @@ -816,7 +844,8 @@ const serviceInstancesDetailedStatisticsRoute = createApmServerRoute({ } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + apmEventClient, + config: setup.config, kuery, start, end, @@ -827,7 +856,7 @@ const serviceInstancesDetailedStatisticsRoute = createApmServerRoute({ kuery, latencyAggregationType, serviceName, - setup, + apmEventClient, transactionType, searchAggregatedTransactions, numBuckets, @@ -901,7 +930,7 @@ export const serviceInstancesMetadataDetails = createApmServerRoute({ | import('./../../../typings/es_schemas/raw/fields/cloud').Cloud | undefined; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const infraMetricsClient = createInfraMetricsClient(resources); const { params } = resources; const { serviceName, serviceNodeName } = params.path; @@ -909,7 +938,7 @@ export const serviceInstancesMetadataDetails = createApmServerRoute({ const serviceInstanceMetadataDetails = await getServiceInstanceMetadataDetails({ - setup, + apmEventClient, serviceName, serviceNodeName, start, @@ -1002,13 +1031,13 @@ export const serviceDependenciesRoute = createApmServerRoute({ location: import('./../../../common/connections').Node; }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { environment, numBuckets, start, end, offset } = params.query; const opts = { - setup, + apmEventClient, start, end, serviceName, @@ -1061,13 +1090,13 @@ export const serviceDependenciesBreakdownRoute = createApmServerRoute({ ): Promise<{ breakdown: Array<{ title: string; data: Array<{ x: number; y: number }> }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { environment, start, end, kuery } = params.query; const breakdown = await getServiceDependenciesBreakdown({ - setup, + apmEventClient, start, end, serviceName, @@ -1177,16 +1206,19 @@ const sortedAndFilteredServicesRoute = createApmServerRoute({ uiSettings: { client: uiSettingsClient }, } = await resources.context.core; - const [setup, serviceGroup, maxNumberOfServices] = await Promise.all([ - setupRequest(resources), - serviceGroupId - ? getServiceGroup({ savedObjectsClient, serviceGroupId }) - : Promise.resolve(null), - uiSettingsClient.get(apmServiceGroupMaxNumberOfServices), - ]); + const [setup, apmEventClient, serviceGroup, maxNumberOfServices] = + await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + serviceGroupId + ? getServiceGroup({ savedObjectsClient, serviceGroupId }) + : Promise.resolve(null), + uiSettingsClient.get(apmServiceGroupMaxNumberOfServices), + ]); return { services: await getSortedAndFilteredServices({ setup, + apmEventClient, start, end, environment, diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_agent_name_by_service.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_agent_name_by_service.ts index f70bd330bfc3e..1ebc98877941f 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_agent_name_by_service.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_agent_name_by_service.ts @@ -6,19 +6,17 @@ */ import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../../lib/helpers/setup_request'; import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { AGENT_NAME } from '../../../../common/elasticsearch_fieldnames'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getAgentNameByService({ serviceName, - setup, + apmEventClient, }: { serviceName: string; - setup: Setup; + apmEventClient: APMEventClient; }) { - const { apmEventClient } = setup; - const params = { terminate_after: 1, apm: { diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_environments/index.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_environments/index.ts index 46ab82152caad..fb335fa231be1 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_environments/index.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration/get_environments/index.ts @@ -10,15 +10,18 @@ import { getAllEnvironments } from '../../../environments/get_all_environments'; import { Setup } from '../../../../lib/helpers/setup_request'; import { getExistingEnvironmentsForService } from './get_existing_environments_for_service'; import { ALL_OPTION_VALUE } from '../../../../../common/agent_configuration/all_option'; +import { APMEventClient } from '../../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getEnvironments({ serviceName, setup, + apmEventClient, searchAggregatedTransactions, size, }: { serviceName: string | undefined; setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; size: number; }) { @@ -27,7 +30,7 @@ export async function getEnvironments({ getAllEnvironments({ searchAggregatedTransactions, serviceName, - setup, + apmEventClient, size, }), getExistingEnvironmentsForService({ serviceName, setup, size }), diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration/queries.test.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration/queries.test.ts index 49a97c1ca4f77..59c52c37601cd 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration/queries.test.ts @@ -24,11 +24,11 @@ describe('agent configuration queries', () => { describe('getAllEnvironments', () => { it('fetches all environments', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getAllEnvironments({ searchAggregatedTransactions: false, serviceName: 'foo', - setup, + apmEventClient, size: 50, }) ); diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts index eb04de4430381..77eb625c0b016 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration/route.ts @@ -25,6 +25,7 @@ import { } from '../../../../common/agent_configuration/runtime_types/agent_configuration_intake_rt'; import { getSearchTransactionsEvents } from '../../../lib/helpers/transactions'; import { syncAgentConfigsToApmPackagePolicies } from '../../fleet/sync_agent_configs_to_apm_package_policies'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; // get list of configurations const agentConfigurationRoute = createApmServerRoute({ @@ -269,13 +270,16 @@ const listAgentConfigurationEnvironmentsRoute = createApmServerRoute({ ): Promise<{ environments: Array<{ name: string; alreadyConfigured: boolean }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { context, params } = resources; const coreContext = await context.core; const { serviceName, start, end } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery: '', start, @@ -287,6 +291,7 @@ const listAgentConfigurationEnvironmentsRoute = createApmServerRoute({ const environments = await getEnvironments({ serviceName, setup, + apmEventClient, searchAggregatedTransactions, size, }); @@ -303,10 +308,13 @@ const agentConfigurationAgentNameRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ agentName: string | undefined }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.query; - const agentName = await getAgentNameByService({ serviceName, setup }); + const agentName = await getAgentNameByService({ + serviceName, + apmEventClient, + }); return { agentName }; }, }); diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts index e30f669b9cd7b..68b4ab7e55c03 100644 --- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts +++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection/route.ts @@ -20,6 +20,7 @@ import { notifyFeatureUsage } from '../../../feature'; import { updateToV3 } from './update_to_v3'; import { environmentStringRt } from '../../../../common/environment_rt'; import { getMlJobsWithAPMGroup } from '../../../lib/anomaly_detection/get_ml_jobs_with_apm_group'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; // get ML anomaly detection jobs for each environment const anomalyDetectionJobsRoute = createApmServerRoute({ @@ -94,11 +95,14 @@ const anomalyDetectionEnvironmentsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/settings/anomaly-detection/environments', options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ environments: string[] }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const coreContext = await resources.context.core; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery: '', }); @@ -108,7 +112,7 @@ const anomalyDetectionEnvironmentsRoute = createApmServerRoute({ const environments = await getAllEnvironments({ includeMissing: true, searchAggregatedTransactions, - setup, + apmEventClient, size, }); diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.test.ts b/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.test.ts index e6ba932ca58d6..afbe4c1d7bbe4 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.test.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.test.ts @@ -10,29 +10,29 @@ import { SearchParamsMock, } from '../../../utils/test_helpers'; import { getTransaction } from './get_transaction'; -import { Setup } from '../../../lib/helpers/setup_request'; import { SERVICE_NAME, TRANSACTION_TYPE, SERVICE_ENVIRONMENT, TRANSACTION_NAME, } from '../../../../common/elasticsearch_fieldnames'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; describe('custom link get transaction', () => { let mock: SearchParamsMock; it('fetches without filter', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTransaction({ - setup: setup as unknown as Setup, + apmEventClient: apmEventClient as unknown as APMEventClient, }) ); expect(mock.params).toMatchSnapshot(); }); it('fetches with all filter', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTransaction({ - setup: setup as unknown as Setup, + apmEventClient: apmEventClient as unknown as APMEventClient, filters: { [SERVICE_NAME]: 'foo', [SERVICE_ENVIRONMENT]: 'bar', diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.ts b/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.ts index 0dfece7bdc34b..d454b447b17f9 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link/get_transaction.ts @@ -8,19 +8,17 @@ import * as t from 'io-ts'; import { compact } from 'lodash'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { Setup } from '../../../lib/helpers/setup_request'; import { filterOptionsRt } from './custom_link_types'; import { splitFilterValueByComma } from './helper'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransaction({ - setup, + apmEventClient, filters = {}, }: { - setup: Setup; + apmEventClient: APMEventClient; filters?: t.TypeOf; }) { - const { apmEventClient } = setup; - const esFilters = compact( Object.entries(filters) // loops through the filters splitting the value by comma and removing white spaces diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link/route.ts b/x-pack/plugins/apm/server/routes/settings/custom_link/route.ts index 1bd6328bdcee5..60d2642acfae3 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link/route.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link/route.ts @@ -19,6 +19,7 @@ import { deleteCustomLink } from './delete_custom_link'; import { getTransaction } from './get_transaction'; import { listCustomLinks } from './list_custom_links'; import { createApmServerRoute } from '../../apm_routes/create_apm_server_route'; +import { getApmEventClient } from '../../../lib/helpers/get_apm_event_client'; const customLinkTransactionRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/settings/custom_links/transaction', @@ -31,12 +32,12 @@ const customLinkTransactionRoute = createApmServerRoute({ ): Promise< import('./../../../../typings/es_schemas/ui/transaction').Transaction > => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { query } = params; // picks only the items listed in FILTER_OPTIONS const filters = pick(query, FILTER_OPTIONS); - return await getTransaction({ setup, filters }); + return await getTransaction({ apmEventClient, filters }); }, }); diff --git a/x-pack/plugins/apm/server/routes/span_links/get_linked_children.ts b/x-pack/plugins/apm/server/routes/span_links/get_linked_children.ts index 172463ff7cf64..60dfe40f469fb 100644 --- a/x-pack/plugins/apm/server/routes/span_links/get_linked_children.ts +++ b/x-pack/plugins/apm/server/routes/span_links/get_linked_children.ts @@ -18,24 +18,22 @@ import { } from '../../../common/elasticsearch_fieldnames'; import type { SpanRaw } from '../../../typings/es_schemas/raw/span_raw'; import type { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; -import { Setup } from '../../lib/helpers/setup_request'; import { getBufferedTimerange } from './utils'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; async function fetchLinkedChildrenOfSpan({ traceId, - setup, + apmEventClient, start, end, spanId, }: { traceId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; spanId?: string; }) { - const { apmEventClient } = setup; - const { startWithBuffer, endWithBuffer } = getBufferedTimerange({ start, end, @@ -83,18 +81,18 @@ function getSpanId(source: TransactionRaw | SpanRaw) { export async function getLinkedChildrenCountBySpanId({ traceId, - setup, + apmEventClient, start, end, }: { traceId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { const linkedChildren = await fetchLinkedChildrenOfSpan({ traceId, - setup, + apmEventClient, start, end, }); @@ -115,20 +113,20 @@ export async function getLinkedChildrenCountBySpanId({ export async function getLinkedChildrenOfSpan({ traceId, spanId, - setup, + apmEventClient, start, end, }: { traceId: string; spanId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { const linkedChildren = await fetchLinkedChildrenOfSpan({ traceId, spanId, - setup, + apmEventClient, start, end, }); diff --git a/x-pack/plugins/apm/server/routes/span_links/get_linked_parents.ts b/x-pack/plugins/apm/server/routes/span_links/get_linked_parents.ts index 3bc8d9ef59419..76efb549bc222 100644 --- a/x-pack/plugins/apm/server/routes/span_links/get_linked_parents.ts +++ b/x-pack/plugins/apm/server/routes/span_links/get_linked_parents.ts @@ -15,10 +15,10 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { SpanRaw } from '../../../typings/es_schemas/raw/span_raw'; import { TransactionRaw } from '../../../typings/es_schemas/raw/transaction_raw'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getLinkedParentsOfSpan({ - setup, + apmEventClient, traceId, spanId, start, @@ -27,13 +27,11 @@ export async function getLinkedParentsOfSpan({ }: { traceId: string; spanId: string; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; processorEvent: ProcessorEvent; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search('get_linked_parents_of_span', { apm: { events: [processorEvent], diff --git a/x-pack/plugins/apm/server/routes/span_links/get_span_links_details.ts b/x-pack/plugins/apm/server/routes/span_links/get_span_links_details.ts index a09d10b422834..c27764e91fbcc 100644 --- a/x-pack/plugins/apm/server/routes/span_links/get_span_links_details.ts +++ b/x-pack/plugins/apm/server/routes/span_links/get_span_links_details.ts @@ -26,24 +26,22 @@ 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 { Setup } from '../../lib/helpers/setup_request'; import { getBufferedTimerange } from './utils'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; async function fetchSpanLinksDetails({ - setup, + apmEventClient, kuery, spanLinks, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; kuery: string; spanLinks: SpanLink[]; start: number; end: number; }) { - const { apmEventClient } = setup; - const { startWithBuffer, endWithBuffer } = getBufferedTimerange({ start, end, @@ -119,13 +117,13 @@ async function fetchSpanLinksDetails({ } export async function getSpanLinksDetails({ - setup, + apmEventClient, spanLinks, kuery, start, end, }: { - setup: Setup; + apmEventClient: APMEventClient; spanLinks: SpanLink[]; kuery: string; start: number; @@ -140,7 +138,7 @@ export async function getSpanLinksDetails({ const chunkedResponses = await Promise.all( spanLinksChunks.map((spanLinksChunk) => fetchSpanLinksDetails({ - setup, + apmEventClient, kuery, spanLinks: spanLinksChunk, start, diff --git a/x-pack/plugins/apm/server/routes/span_links/route.ts b/x-pack/plugins/apm/server/routes/span_links/route.ts index 34b5864778144..18f361503d374 100644 --- a/x-pack/plugins/apm/server/routes/span_links/route.ts +++ b/x-pack/plugins/apm/server/routes/span_links/route.ts @@ -5,7 +5,6 @@ * 2.0. */ import * as t from 'io-ts'; -import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getSpanLinksDetails } from './get_span_links_details'; import { getLinkedChildrenOfSpan } from './get_linked_children'; @@ -13,6 +12,7 @@ import { kueryRt, rangeRt } from '../default_api_types'; import { SpanLinkDetails } from '../../../common/span_links'; import { processorEventRt } from '../../../common/processor_event'; import { getLinkedParentsOfSpan } from './get_linked_parents'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const linkedParentsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents', @@ -36,9 +36,9 @@ const linkedParentsRoute = createApmServerRoute({ const { params: { query, path }, } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const linkedParents = await getLinkedParentsOfSpan({ - setup, + apmEventClient, traceId: path.traceId, spanId: path.spanId, start: query.start, @@ -48,7 +48,7 @@ const linkedParentsRoute = createApmServerRoute({ return { spanLinksDetails: await getSpanLinksDetails({ - setup, + apmEventClient, spanLinks: linkedParents, kuery: query.kuery, start: query.start, @@ -76,9 +76,9 @@ const linkedChildrenRoute = createApmServerRoute({ const { params: { query, path }, } = resources; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const linkedChildren = await getLinkedChildrenOfSpan({ - setup, + apmEventClient, traceId: path.traceId, spanId: path.spanId, start: query.start, @@ -87,7 +87,7 @@ const linkedChildrenRoute = createApmServerRoute({ return { spanLinksDetails: await getSpanLinksDetails({ - setup, + apmEventClient, spanLinks: linkedChildren, kuery: query.kuery, start: query.start, diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/get_service_statistics.ts b/x-pack/plugins/apm/server/routes/storage_explorer/get_service_statistics.ts index 169bff0cd7113..95a74d13c1313 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/get_service_statistics.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/get_service_statistics.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { termQuery, kqlQuery, @@ -34,9 +33,11 @@ import { getEstimatedSizeForDocumentsInIndex, } from './indices_stats_helpers'; import { RandomSampler } from '../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; async function getMainServiceStatistics({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -46,6 +47,7 @@ async function getMainServiceStatistics({ kuery, }: { setup: Setup; + apmEventClient: APMEventClient; context: ApmPluginRequestHandlerContext; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; randomSampler: RandomSampler; @@ -54,8 +56,6 @@ async function getMainServiceStatistics({ environment: string; kuery: string; }) { - const { apmEventClient } = setup; - const [{ indices: allIndicesStats }, response] = await Promise.all([ getTotalIndicesStats({ context, setup }), apmEventClient.search('get_main_service_statistics', { @@ -82,7 +82,7 @@ async function getMainServiceStatistics({ indexLifeCyclePhaseToDataTier[indexLifecyclePhase] ) : []), - ] as QueryDslQueryContainer[], + ], }, }, aggs: { @@ -177,6 +177,7 @@ async function getMainServiceStatistics({ export async function getServiceStatistics({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -187,6 +188,7 @@ export async function getServiceStatistics({ searchAggregatedTransactions, }: { setup: Setup; + apmEventClient: APMEventClient; context: ApmPluginRequestHandlerContext; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; randomSampler: RandomSampler; @@ -200,6 +202,7 @@ export async function getServiceStatistics({ await Promise.all([ getMainServiceStatistics({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -209,7 +212,7 @@ export async function getServiceStatistics({ end, }), getTotalTransactionsPerService({ - setup, + apmEventClient, searchAggregatedTransactions, indexLifecyclePhase, randomSampler, diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/get_size_timeseries.ts b/x-pack/plugins/apm/server/routes/storage_explorer/get_size_timeseries.ts index f513efe059f71..2337243a64123 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/get_size_timeseries.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/get_size_timeseries.ts @@ -29,11 +29,13 @@ import { getTotalIndicesStats, getEstimatedSizeForDocumentsInIndex, } from './indices_stats_helpers'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getSizeTimeseries({ environment, kuery, setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -44,6 +46,7 @@ export async function getSizeTimeseries({ environment: string; kuery: string; setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; @@ -51,8 +54,6 @@ export async function getSizeTimeseries({ randomSampler: RandomSampler; context: ApmPluginRequestHandlerContext; }) { - const { apmEventClient } = setup; - const { intervalString } = getBucketSizeForAggregatedTransactions({ start, end, diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_processor_event.ts b/x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_service.ts similarity index 55% rename from x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_processor_event.ts rename to x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_service.ts index 8f92ce9d93809..ae6b0a82ca0e7 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_processor_event.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/get_storage_details_per_service.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { termQuery, kqlQuery, @@ -28,11 +27,15 @@ import { ApmPluginRequestHandlerContext } from '../typings'; import { getTotalIndicesStats, getEstimatedSizeForDocumentsInIndex, + getIndicesLifecycleStatus, + getIndicesInfo, } from './indices_stats_helpers'; import { RandomSampler } from '../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getStorageDetailsPerProcessorEvent({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -43,6 +46,7 @@ export async function getStorageDetailsPerProcessorEvent({ serviceName, }: { setup: Setup; + apmEventClient: APMEventClient; context: ApmPluginRequestHandlerContext; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; randomSampler: RandomSampler; @@ -52,11 +56,9 @@ export async function getStorageDetailsPerProcessorEvent({ kuery: string; serviceName: string; }) { - const { apmEventClient } = setup; - const [{ indices: allIndicesStats }, response] = await Promise.all([ getTotalIndicesStats({ setup, context }), - apmEventClient.search('get_storage_details_per_processor_event', { + apmEventClient.search('get_storage_details_per_service', { apm: { events: [ ProcessorEvent.span, @@ -81,7 +83,7 @@ export async function getStorageDetailsPerProcessorEvent({ indexLifeCyclePhaseToDataTier[indexLifecyclePhase] ) : []), - ] as QueryDslQueryContainer[], + ], }, }, aggs: { @@ -153,3 +155,122 @@ export async function getStorageDetailsPerProcessorEvent({ }; }); } + +export async function getStorageDetailsPerIndex({ + apmEventClient, + setup, + context, + indexLifecyclePhase, + randomSampler, + start, + end, + environment, + kuery, + serviceName, +}: { + apmEventClient: APMEventClient; + setup: Setup; + context: ApmPluginRequestHandlerContext; + indexLifecyclePhase: IndexLifecyclePhaseSelectOption; + randomSampler: RandomSampler; + start: number; + end: number; + environment: string; + kuery: string; + serviceName: string; +}) { + const [ + { indices: allIndicesStats }, + indicesLifecycleStatus, + indicesInfo, + response, + ] = await Promise.all([ + getTotalIndicesStats({ setup, context }), + getIndicesLifecycleStatus({ setup, context }), + getIndicesInfo({ setup, context }), + apmEventClient.search('get_storage_details_per_index', { + apm: { + events: [ + ProcessorEvent.span, + ProcessorEvent.transaction, + ProcessorEvent.error, + ProcessorEvent.metric, + ], + }, + body: { + size: 0, + track_total_hits: false, + query: { + bool: { + filter: [ + ...environmentQuery(environment), + ...kqlQuery(kuery), + ...rangeQuery(start, end), + ...termQuery(SERVICE_NAME, serviceName), + ...(indexLifecyclePhase !== IndexLifecyclePhaseSelectOption.All + ? termQuery( + TIER, + indexLifeCyclePhaseToDataTier[indexLifecyclePhase] + ) + : []), + ], + }, + }, + aggs: { + sample: { + random_sampler: randomSampler, + aggs: { + indices: { + terms: { + field: INDEX, + size: 500, + }, + aggs: { + number_of_metric_docs_for_index: { + value_count: { + field: INDEX, + }, + }, + }, + }, + }, + }, + }, + }, + }), + ]); + + return ( + response.aggregations?.sample.indices.buckets.map((bucket) => { + const indexName = bucket.key as string; + const numberOfDocs = bucket.number_of_metric_docs_for_index.value; + const indexInfo = indicesInfo[indexName]; + const indexLifecycle = indicesLifecycleStatus[indexName]; + + const size = + allIndicesStats && + getEstimatedSizeForDocumentsInIndex({ + allIndicesStats, + indexName, + numberOfDocs, + }); + + return { + indexName, + numberOfDocs, + primary: indexInfo + ? indexInfo.settings?.index?.number_of_shards ?? 0 + : undefined, + replica: indexInfo + ? indexInfo.settings?.number_of_replicas ?? 0 + : undefined, + size, + dataStream: indexInfo?.data_stream, + lifecyclePhase: + indexLifecycle && 'phase' in indexLifecycle + ? indexLifecycle.phase + : undefined, + }; + }) ?? [] + ); +} diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/get_summary_statistics.ts b/x-pack/plugins/apm/server/routes/storage_explorer/get_summary_statistics.ts index dfa510e8f0890..2bb471751385c 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/get_summary_statistics.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/get_summary_statistics.ts @@ -6,7 +6,6 @@ */ import { ProcessorEvent } from '@kbn/observability-plugin/common'; -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { termQuery, kqlQuery, @@ -15,6 +14,7 @@ import { import { getTotalIndicesStats, getEstimatedSizeForDocumentsInIndex, + getApmDiskSpacedUsedPct, } from './indices_stats_helpers'; import { Setup } from '../../lib/helpers/setup_request'; import { ApmPluginRequestHandlerContext } from '../typings'; @@ -36,9 +36,10 @@ import { isRootTransaction, } from '../../lib/helpers/transactions'; import { calculateThroughputWithRange } from '../../lib/helpers/calculate_throughput'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTracesPerMinute({ - setup, + apmEventClient, indexLifecyclePhase, start, end, @@ -46,7 +47,7 @@ export async function getTracesPerMinute({ kuery, searchAggregatedTransactions, }: { - setup: Setup; + apmEventClient: APMEventClient; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; start: number; end: number; @@ -54,8 +55,6 @@ export async function getTracesPerMinute({ kuery: string; searchAggregatedTransactions: boolean; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search('get_traces_per_minute', { apm: { events: [getProcessorEventForTransactions(searchAggregatedTransactions)], @@ -103,6 +102,7 @@ export async function getTracesPerMinute({ export async function getMainSummaryStats({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -112,6 +112,7 @@ export async function getMainSummaryStats({ kuery, }: { setup: Setup; + apmEventClient: APMEventClient; context: ApmPluginRequestHandlerContext; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; randomSampler: RandomSampler; @@ -120,10 +121,9 @@ export async function getMainSummaryStats({ environment: string; kuery: string; }) { - const { apmEventClient } = setup; - - const [{ indices: allIndicesStats }, res] = await Promise.all([ + const [totalIndicesStats, totalDiskSpace, res] = await Promise.all([ getTotalIndicesStats({ context, setup }), + getApmDiskSpacedUsedPct(context), apmEventClient.search('get_storage_explorer_main_summary_stats', { apm: { events: [ @@ -148,7 +148,7 @@ export async function getMainSummaryStats({ indexLifeCyclePhaseToDataTier[indexLifecyclePhase] ) : []), - ] as QueryDslQueryContainer[], + ], }, }, aggs: { @@ -180,7 +180,8 @@ export async function getMainSummaryStats({ }), ]); - const estimatedSize = allIndicesStats + const { indices: allIndicesStats } = totalIndicesStats; + const estimatedIncrementalSize = allIndicesStats ? res.aggregations?.sample.indices.buckets.reduce((prev, curr) => { return ( prev + @@ -194,10 +195,13 @@ export async function getMainSummaryStats({ : 0; const durationAsDays = (end - start) / 1000 / 60 / 60 / 24; + const totalApmSize = totalIndicesStats._all.total?.store?.size_in_bytes ?? 0; return { + totalSize: totalApmSize, + diskSpaceUsedPct: totalApmSize / totalDiskSpace, numberOfServices: res.aggregations?.services_count.value ?? 0, - estimatedSize, - dailyDataGeneration: estimatedSize / durationAsDays, + estimatedIncrementalSize, + dailyDataGeneration: estimatedIncrementalSize / durationAsDays, }; } diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/get_total_transactions_per_service.ts b/x-pack/plugins/apm/server/routes/storage_explorer/get_total_transactions_per_service.ts index 793c69ab71b4c..1f883adee3f70 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/get_total_transactions_per_service.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/get_total_transactions_per_service.ts @@ -4,13 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { termQuery, kqlQuery, rangeQuery, } from '@kbn/observability-plugin/server'; -import { Setup } from '../../lib/helpers/setup_request'; import { getProcessorEventForTransactions, getDocumentTypeFilterForTransactions, @@ -22,9 +20,10 @@ import { } from '../../../common/storage_explorer_types'; import { environmentQuery } from '../../../common/utils/environment_query'; import { RandomSampler } from '../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTotalTransactionsPerService({ - setup, + apmEventClient, searchAggregatedTransactions, indexLifecyclePhase, randomSampler, @@ -33,7 +32,7 @@ export async function getTotalTransactionsPerService({ environment, kuery, }: { - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; indexLifecyclePhase: IndexLifecyclePhaseSelectOption; randomSampler: RandomSampler; @@ -42,8 +41,6 @@ export async function getTotalTransactionsPerService({ environment: string; kuery: string; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_total_transactions_per_service', { @@ -70,7 +67,7 @@ export async function getTotalTransactionsPerService({ indexLifeCyclePhaseToDataTier[indexLifecyclePhase] ) : []), - ] as QueryDslQueryContainer[], + ], }, }, aggs: { diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/has_storage_explorer_privileges.ts b/x-pack/plugins/apm/server/routes/storage_explorer/has_storage_explorer_privileges.ts index 714c0eee1c62c..e69c3f33f8d9d 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/has_storage_explorer_privileges.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/has_storage_explorer_privileges.ts @@ -28,17 +28,19 @@ export async function hasStorageExplorerPrivileges({ ); const esClient = (await context.core).elasticsearch.client; - const { index } = await esClient.asCurrentUser.security.hasPrivileges({ - body: { - index: [ - { - names, - privileges: ['monitor'], - }, - ], - }, - }); + const { index, cluster } = + await esClient.asCurrentUser.security.hasPrivileges({ + body: { + index: [ + { + names, + privileges: ['monitor'], + }, + ], + cluster: ['monitor'], + }, + }); - const hasPrivileges = every(index, 'monitor'); + const hasPrivileges = cluster.monitor && every(index, 'monitor'); return hasPrivileges; } diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/indices_stats_helpers.ts b/x-pack/plugins/apm/server/routes/storage_explorer/indices_stats_helpers.ts index aa9e854cc4a29..d7ce952b2fd50 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/indices_stats_helpers.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/indices_stats_helpers.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { uniq } from 'lodash'; +import { uniq, values, sumBy } from 'lodash'; import { IndicesStatsIndicesStats } from '@elastic/elasticsearch/lib/api/types'; import { Setup } from '../../lib/helpers/setup_request'; import { ApmPluginRequestHandlerContext } from '../typings'; @@ -16,10 +16,7 @@ export async function getTotalIndicesStats({ context: ApmPluginRequestHandlerContext; setup: Setup; }) { - const { - indices: { transaction, span, metric, error }, - } = setup; - const index = uniq([transaction, span, metric, error]).join(); + const index = getApmIndicesCombined(setup); const esClient = (await context.core).elasticsearch.client; const totalStats = await esClient.asCurrentUser.indices.stats({ index }); return totalStats; @@ -44,3 +41,67 @@ export function getEstimatedSizeForDocumentsInIndex({ return estimatedSize; } + +export async function getApmDiskSpacedUsedPct( + context: ApmPluginRequestHandlerContext +) { + const esClient = (await context.core).elasticsearch.client; + const { nodes: diskSpacePerNode } = await esClient.asCurrentUser.nodes.stats({ + metric: 'fs', + filter_path: 'nodes.*.fs.total.total_in_bytes', + }); + + const totalDiskSpace = sumBy( + values(diskSpacePerNode), + (node) => node?.fs?.total?.total_in_bytes ?? 0 + ); + + return totalDiskSpace; +} + +export async function getIndicesLifecycleStatus({ + context, + setup, +}: { + context: ApmPluginRequestHandlerContext; + setup: Setup; +}) { + const index = getApmIndicesCombined(setup); + const esClient = (await context.core).elasticsearch.client; + const { indices } = await esClient.asCurrentUser.ilm.explainLifecycle({ + index, + filter_path: 'indices.*.phase', + }); + + return indices; +} + +export async function getIndicesInfo({ + context, + setup, +}: { + context: ApmPluginRequestHandlerContext; + setup: Setup; +}) { + const index = getApmIndicesCombined(setup); + const esClient = (await context.core).elasticsearch.client; + const indicesInfo = await esClient.asCurrentUser.indices.get({ + index, + filter_path: [ + '*.settings.index.number_of_shards', + '*.settings.index.number_of_replicas', + '*.data_stream', + ], + features: ['settings'], + }); + + return indicesInfo; +} + +export function getApmIndicesCombined(setup: Setup) { + const { + indices: { transaction, span, metric, error }, + } = setup; + + return uniq([transaction, span, metric, error]).join(); +} diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.test.ts b/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.test.ts new file mode 100644 index 0000000000000..d24ef35158170 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.test.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Setup } from '../../lib/helpers/setup_request'; +import { isCrossClusterSearch } from './is_cross_cluster_search'; +import { ApmIndicesConfig } from '@kbn/observability-plugin/common/typings'; + +describe('isCrossClusterSearch', () => { + it('returns false when there are no remote clusters in APM indices', () => { + const mockedSetup = { + indices: { + transaction: 'traces-apm*', + span: 'traces-apm*', + metric: 'metrics-apm*', + error: 'logs-apm*', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(false); + }); + + it('returns false when there are multiple indices per type and no remote clusters in APM indices', () => { + const mockedSetup = { + indices: { + transaction: 'traces-apm*,test-apm*', + span: 'traces-apm*,test-apm*', + metric: 'metrics-apm*,test-apm*', + error: 'logs-apm*,test-apm*', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(false); + }); + + it('returns false when there are remote clusters in onboarding and sourcemap indices', () => { + const mockedSetup = { + indices: { + transaction: '', + span: '', + metric: '', + error: '', + onboarding: 'apm-*,remote_cluster:apm-*', + sourcemap: 'apm-*,remote_cluster:apm-*', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(false); + }); + + it('returns true when there are remote clusters in transaction indices', () => { + const mockedSetup = { + indices: { + transaction: 'traces-apm*,remote_cluster:traces-apm*', + span: '', + metric: '', + error: '', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(true); + }); + + it('returns true when there are remote clusters in span indices', () => { + const mockedSetup = { + indices: { + transaction: '', + span: 'traces-apm*,remote_cluster:traces-apm*', + metric: '', + error: '', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(true); + }); + + it('returns true when there are remote clusters in metrics indices', () => { + const mockedSetup = { + indices: { + transaction: '', + span: '', + metric: 'metrics-apm*,remote_cluster:metrics-apm*', + error: '', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(true); + }); + + it('returns true when there are remote clusters in error indices', () => { + const mockedSetup = { + indices: { + transaction: '', + span: '', + metric: '', + error: 'logs-apm*,remote_cluster:logs-apm*', + } as ApmIndicesConfig, + } as unknown as Setup; + + expect(isCrossClusterSearch(mockedSetup)).toBe(true); + }); +}); diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.ts b/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.ts new file mode 100644 index 0000000000000..fbbe7a1347931 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/storage_explorer/is_cross_cluster_search.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 { Setup } from '../../lib/helpers/setup_request'; +import { getApmIndicesCombined } from './indices_stats_helpers'; + +export function isCrossClusterSearch(setup: Setup) { + // Check if a remote cluster is set in APM indices + return getApmIndicesCombined(setup).includes(':'); +} diff --git a/x-pack/plugins/apm/server/routes/storage_explorer/route.ts b/x-pack/plugins/apm/server/routes/storage_explorer/route.ts index 9d817efbf34f5..6872bdd22a09d 100644 --- a/x-pack/plugins/apm/server/routes/storage_explorer/route.ts +++ b/x-pack/plugins/apm/server/routes/storage_explorer/route.ts @@ -9,10 +9,14 @@ import * as t from 'io-ts'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; +import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { setupRequest } from '../../lib/helpers/setup_request'; -import { indexLifecyclePhaseRt } from '../../../common/storage_explorer_types'; +import { + indexLifecyclePhaseRt, + IndexLifecyclePhaseSelectOption, +} from '../../../common/storage_explorer_types'; import { getServiceStatistics } from './get_service_statistics'; import { probabilityRt, @@ -21,7 +25,10 @@ import { rangeRt, } from '../default_api_types'; import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; -import { getStorageDetailsPerProcessorEvent } from './get_storage_details_per_processor_event'; +import { + getStorageDetailsPerIndex, + getStorageDetailsPerProcessorEvent, +} from './get_storage_details_per_service'; import { getRandomSampler } from '../../lib/helpers/get_random_sampler'; import { getSizeTimeseries } from './get_size_timeseries'; import { hasStorageExplorerPrivileges } from './has_storage_explorer_privileges'; @@ -29,6 +36,9 @@ import { getMainSummaryStats, getTracesPerMinute, } from './get_summary_statistics'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; +import { isCrossClusterSearch } from './is_cross_cluster_search'; +import { getServiceNamesFromTermsEnum } from '../services/get_services/get_sorted_and_filtered_services'; const storageExplorerRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/storage_explorer', @@ -71,19 +81,21 @@ const storageExplorerRoute = createApmServerRoute({ }, } = params; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery, }); const serviceStatistics = await getServiceStatistics({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -127,6 +139,15 @@ const storageExplorerServiceDetailsRoute = createApmServerRoute({ docs: number; size: number; }>; + indicesStats: Array<{ + indexName: string; + numberOfDocs: number; + primary?: number | string; + replica?: number | string; + size?: number; + dataStream?: string; + lifecyclePhase?: string; + }>; }> => { const { params, @@ -147,24 +168,40 @@ const storageExplorerServiceDetailsRoute = createApmServerRoute({ }, } = params; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); - const processorEventStats = await getStorageDetailsPerProcessorEvent({ - setup, - context, - indexLifecyclePhase, - randomSampler, - environment, - kuery, - start, - end, - serviceName, - }); + const [processorEventStats, indicesStats] = await Promise.all([ + getStorageDetailsPerProcessorEvent({ + apmEventClient, + setup, + context, + indexLifecyclePhase, + randomSampler, + environment, + kuery, + start, + end, + serviceName, + }), + getStorageDetailsPerIndex({ + apmEventClient, + setup, + context, + indexLifecyclePhase, + randomSampler, + environment, + kuery, + start, + end, + serviceName, + }), + ]); - return { processorEventStats }; + return { processorEventStats, indicesStats }; }, }); @@ -206,13 +243,14 @@ const storageChartRoute = createApmServerRoute({ }, } = params; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery, }); @@ -226,6 +264,7 @@ const storageChartRoute = createApmServerRoute({ start, end, setup, + apmEventClient, context, }); @@ -274,7 +313,9 @@ const storageExplorerSummaryStatsRoute = createApmServerRoute({ ): Promise<{ tracesPerMinute: number; numberOfServices: number; - estimatedSize: number; + totalSize: number; + diskSpaceUsedPct: number; + estimatedIncrementalSize: number; dailyDataGeneration: number; }> => { const { @@ -295,13 +336,14 @@ const storageExplorerSummaryStatsRoute = createApmServerRoute({ }, } = params; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery, }); @@ -309,6 +351,7 @@ const storageExplorerSummaryStatsRoute = createApmServerRoute({ const [mainSummaryStats, tracesPerMinute] = await Promise.all([ getMainSummaryStats({ setup, + apmEventClient, context, indexLifecyclePhase, randomSampler, @@ -318,7 +361,7 @@ const storageExplorerSummaryStatsRoute = createApmServerRoute({ kuery, }), getTracesPerMinute({ - setup, + apmEventClient, indexLifecyclePhase, start, end, @@ -335,12 +378,68 @@ const storageExplorerSummaryStatsRoute = createApmServerRoute({ }, }); +const storageExplorerIsCrossClusterSearchRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/storage_explorer/is_cross_cluster_search', + options: { tags: ['access:apm'] }, + handler: async (resources): Promise<{ isCrossClusterSearch: boolean }> => { + const setup = await setupRequest(resources); + return { isCrossClusterSearch: isCrossClusterSearch(setup) }; + }, +}); + +const storageExplorerGetServices = createApmServerRoute({ + endpoint: 'GET /internal/apm/storage_explorer/get_services', + options: { + tags: ['access:apm'], + }, + params: t.type({ + query: t.intersection([indexLifecyclePhaseRt, environmentRt, kueryRt]), + }), + handler: async ( + resources + ): Promise<{ + services: Array<{ + serviceName: string; + }>; + }> => { + const { + query: { environment, kuery, indexLifecyclePhase }, + } = resources.params; + + if ( + kuery || + indexLifecyclePhase !== IndexLifecyclePhaseSelectOption.All || + environment !== ENVIRONMENT_ALL.value + ) { + return { + services: [], + }; + } + + const apmEventClient = await getApmEventClient(resources); + + const services = await getServiceNamesFromTermsEnum({ + apmEventClient, + environment, + maxNumberOfServices: 500, + }); + + return { + services: services.map((serviceName): { serviceName: string } => ({ + serviceName, + })), + }; + }, +}); + export const storageExplorerRouteRepository = { ...storageExplorerRoute, ...storageExplorerServiceDetailsRoute, ...storageChartRoute, ...storageExplorerPrivilegesRoute, ...storageExplorerSummaryStatsRoute, + ...storageExplorerIsCrossClusterSearchRoute, + ...storageExplorerGetServices, }; const SECURITY_REQUIRED_MESSAGE = i18n.translate( diff --git a/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_aggregation.ts b/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_aggregation.ts index 56ed34805c2fb..856b6bea1b9a5 100644 --- a/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_aggregation.ts +++ b/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_aggregation.ts @@ -8,14 +8,14 @@ import { rangeQuery, termQuery } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getSuggestionsWithTermsAggregation({ fieldName, fieldValue, searchAggregatedTransactions, serviceName, - setup, + apmEventClient, size, start, end, @@ -24,13 +24,11 @@ export async function getSuggestionsWithTermsAggregation({ fieldValue: string; searchAggregatedTransactions: boolean; serviceName?: string; - setup: Setup; + apmEventClient: APMEventClient; size: number; start: number; end: number; }) { - const { apmEventClient } = setup; - const response = await apmEventClient.search( 'get_suggestions_with_terms_aggregation', { diff --git a/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_enum.ts b/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_enum.ts index 4437a36151895..c945438ff01cf 100644 --- a/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_enum.ts +++ b/x-pack/plugins/apm/server/routes/suggestions/get_suggestions_with_terms_enum.ts @@ -6,13 +6,13 @@ */ import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { getProcessorEventForTransactions } from '../../lib/helpers/transactions'; -import { Setup } from '../../lib/helpers/setup_request'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getSuggestionsWithTermsEnum({ fieldName, fieldValue, searchAggregatedTransactions, - setup, + apmEventClient, size, start, end, @@ -20,40 +20,35 @@ export async function getSuggestionsWithTermsEnum({ fieldName: string; fieldValue: string; searchAggregatedTransactions: boolean; - setup: Setup; + apmEventClient: APMEventClient; size: number; start: number; end: number; }) { - const { apmEventClient } = setup; - - const response = await apmEventClient.termsEnum( - 'get_suggestions_with_terms_enum', - { - apm: { - events: [ - getProcessorEventForTransactions(searchAggregatedTransactions), - ProcessorEvent.error, - ProcessorEvent.metric, - ], - }, - body: { - case_insensitive: true, - field: fieldName, - size, - string: fieldValue, - index_filter: { - range: { - ['@timestamp']: { - gte: start, - lte: end, - format: 'epoch_millis', - }, + const response = await apmEventClient.termsEnum('get_suggestions', { + apm: { + events: [ + getProcessorEventForTransactions(searchAggregatedTransactions), + ProcessorEvent.error, + ProcessorEvent.metric, + ], + }, + body: { + case_insensitive: true, + field: fieldName, + size, + string: fieldValue, + index_filter: { + range: { + ['@timestamp']: { + gte: start, + lte: end, + format: 'epoch_millis', }, }, }, - } - ); + }, + }); return { terms: response.terms }; } diff --git a/x-pack/plugins/apm/server/routes/suggestions/route.ts b/x-pack/plugins/apm/server/routes/suggestions/route.ts index f0396ac62ca51..976779816f960 100644 --- a/x-pack/plugins/apm/server/routes/suggestions/route.ts +++ b/x-pack/plugins/apm/server/routes/suggestions/route.ts @@ -13,6 +13,7 @@ import { getSearchTransactionsEvents } from '../../lib/helpers/transactions'; import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { rangeRt } from '../default_api_types'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const suggestionsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/suggestions', @@ -28,11 +29,14 @@ const suggestionsRoute = createApmServerRoute({ }), options: { tags: ['access:apm'] }, handler: async (resources): Promise<{ terms: string[] }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { context, params } = resources; const { fieldName, fieldValue, serviceName, start, end } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - apmEventClient: setup.apmEventClient, + apmEventClient, config: setup.config, kuery: '', }); @@ -46,7 +50,7 @@ const suggestionsRoute = createApmServerRoute({ fieldName, fieldValue, searchAggregatedTransactions, - setup, + apmEventClient, size, start, end, @@ -65,7 +69,7 @@ const suggestionsRoute = createApmServerRoute({ fieldValue, searchAggregatedTransactions, serviceName, - setup, + apmEventClient, size, start, end, diff --git a/x-pack/plugins/apm/server/routes/time_range_metadata/route.ts b/x-pack/plugins/apm/server/routes/time_range_metadata/route.ts index f0321f4cfde4d..bde0058b57560 100644 --- a/x-pack/plugins/apm/server/routes/time_range_metadata/route.ts +++ b/x-pack/plugins/apm/server/routes/time_range_metadata/route.ts @@ -7,7 +7,7 @@ import { toBooleanRt } from '@kbn/io-ts-utils'; import * as t from 'io-ts'; import { TimeRangeMetadata } from '../../../common/time_range_metadata'; -import { setupRequest } from '../../lib/helpers/setup_request'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; import { getIsUsingServiceDestinationMetrics } from '../../lib/helpers/spans/get_is_using_service_destination_metrics'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { kueryRt, rangeRt } from '../default_api_types'; @@ -25,7 +25,7 @@ export const timeRangeMetadataRoute = createApmServerRoute({ tags: ['access:apm'], }, handler: async (resources): Promise => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { query: { useSpanName, start, end, kuery }, @@ -33,7 +33,7 @@ export const timeRangeMetadataRoute = createApmServerRoute({ const [isUsingServiceDestinationMetrics] = await Promise.all([ getIsUsingServiceDestinationMetrics({ - setup, + apmEventClient, useSpanName, start, end, diff --git a/x-pack/plugins/apm/server/routes/traces/get_top_traces_primary_stats.ts b/x-pack/plugins/apm/server/routes/traces/get_top_traces_primary_stats.ts index 92ff9c38260df..dc9143685902b 100644 --- a/x-pack/plugins/apm/server/routes/traces/get_top_traces_primary_stats.ts +++ b/x-pack/plugins/apm/server/routes/traces/get_top_traces_primary_stats.ts @@ -13,7 +13,6 @@ import { } from '@kbn/observability-plugin/server'; import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; import { withApmSpan } from '../../utils/with_apm_span'; -import { Setup } from '../../lib/helpers/setup_request'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; import { environmentQuery } from '../../../common/utils/environment_query'; import { calculateImpactBuilder } from './calculate_impact_builder'; @@ -31,6 +30,7 @@ import { TRANSACTION_NAME, } from '../../../common/elasticsearch_fieldnames'; import { RandomSampler } from '../../lib/helpers/get_random_sampler'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export type BucketKey = Record< typeof TRANSACTION_NAME | typeof SERVICE_NAME, @@ -44,7 +44,7 @@ interface TopTracesParams { searchAggregatedTransactions: boolean; start: number; end: number; - setup: Setup; + apmEventClient: APMEventClient; randomSampler: RandomSampler; } export async function getTopTracesPrimaryStats({ @@ -54,11 +54,11 @@ export async function getTopTracesPrimaryStats({ searchAggregatedTransactions, start, end, - setup, + apmEventClient, randomSampler, }: TopTracesParams) { return withApmSpan('get_top_traces_primary_stats', async () => { - const response = await setup.apmEventClient.search( + const response = await apmEventClient.search( 'get_transaction_group_stats', { apm: { diff --git a/x-pack/plugins/apm/server/routes/traces/get_trace_items.ts b/x-pack/plugins/apm/server/routes/traces/get_trace_items.ts index f1c1efc8664ce..960924e1aa4b0 100644 --- a/x-pack/plugins/apm/server/routes/traces/get_trace_items.ts +++ b/x-pack/plugins/apm/server/routes/traces/get_trace_items.ts @@ -18,16 +18,17 @@ import { TRACE_ID, TRANSACTION_DURATION, } from '../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../lib/helpers/setup_request'; import { getLinkedChildrenCountBySpanId } from '../span_links/get_linked_children'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; +import { APMConfig } from '../..'; export async function getTraceItems( traceId: string, - setup: Setup, + config: APMConfig, + apmEventClient: APMEventClient, start: number, end: number ) { - const { apmEventClient, config } = setup; const maxTraceItems = config.ui.maxTraceItems; const excludedLogLevels = ['debug', 'info', 'warning']; @@ -80,7 +81,7 @@ export async function getTraceItems( await Promise.all([ errorResponsePromise, traceResponsePromise, - getLinkedChildrenCountBySpanId({ traceId, setup, start, end }), + getLinkedChildrenCountBySpanId({ traceId, apmEventClient, start, end }), ]); const exceedsMax = traceResponse.hits.total.value > maxTraceItems; diff --git a/x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts b/x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts index 5f56d20dbbf9f..2e74d104592d7 100644 --- a/x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts +++ b/x-pack/plugins/apm/server/routes/traces/get_trace_samples_by_query.ts @@ -11,7 +11,6 @@ import { } from '@kbn/observability-plugin/server'; import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { Environment } from '../../../common/environment_rt'; -import { Setup } from '../../lib/helpers/setup_request'; import { TraceSearchType } from '../../../common/trace_explorer'; import { environmentQuery } from '../../../common/utils/environment_query'; import { @@ -22,16 +21,17 @@ import { TRANSACTION_SAMPLED, } from '../../../common/elasticsearch_fieldnames'; import { asMutableArray } from '../../../common/utils/as_mutable_array'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTraceSamplesByQuery({ - setup, + apmEventClient, start, end, environment, query, type, }: { - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; environment: Environment; @@ -45,7 +45,7 @@ export async function getTraceSamplesByQuery({ if (type === TraceSearchType.kql) { traceIds = ( - await setup.apmEventClient.search('get_trace_ids_by_kql_query', { + await apmEventClient.search('get_trace_ids_by_kql_query', { apm: { events: [ ProcessorEvent.transaction, @@ -81,7 +81,7 @@ export async function getTraceSamplesByQuery({ } else if (type === TraceSearchType.eql) { traceIds = ( - await setup.apmEventClient.eqlSearch('get_trace_ids_by_eql_query', { + await apmEventClient.eqlSearch('get_trace_ids_by_eql_query', { apm: { events: [ ProcessorEvent.transaction, @@ -115,7 +115,7 @@ export async function getTraceSamplesByQuery({ return []; } - const traceSamplesResponse = await setup.apmEventClient.search( + const traceSamplesResponse = await apmEventClient.search( 'get_trace_samples_by_trace_ids', { apm: { diff --git a/x-pack/plugins/apm/server/routes/traces/queries.test.ts b/x-pack/plugins/apm/server/routes/traces/queries.test.ts index f1bd97fd88ebd..64e08d11acc74 100644 --- a/x-pack/plugins/apm/server/routes/traces/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/traces/queries.test.ts @@ -19,8 +19,8 @@ describe('trace queries', () => { }); it('fetches a trace', async () => { - mock = await inspectSearchParams((setup) => - getTraceItems('foo', setup, 0, 50000) + mock = await inspectSearchParams((setup, apmEventClient) => + getTraceItems('foo', setup.config, apmEventClient, 0, 50000) ); expect(mock.params).toMatchSnapshot(); diff --git a/x-pack/plugins/apm/server/routes/traces/route.ts b/x-pack/plugins/apm/server/routes/traces/route.ts index 336e862bcd09d..dc1408f9bddef 100644 --- a/x-pack/plugins/apm/server/routes/traces/route.ts +++ b/x-pack/plugins/apm/server/routes/traces/route.ts @@ -22,6 +22,7 @@ import { getTopTracesPrimaryStats } from './get_top_traces_primary_stats'; import { getTraceItems } from './get_trace_items'; import { getTraceSamplesByQuery } from './get_trace_samples_by_query'; import { getRandomSampler } from '../../lib/helpers/get_random_sampler'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const tracesRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/traces', @@ -51,13 +52,15 @@ const tracesRoute = createApmServerRoute({ const { environment, kuery, start, end, probability } = params.query; - const [setup, randomSampler] = await Promise.all([ + const [setup, apmEventClient, randomSampler] = await Promise.all([ setupRequest(resources), + getApmEventClient(resources), getRandomSampler({ security, request, probability }), ]); const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + apmEventClient, + config: setup.config, kuery, start, end, @@ -66,7 +69,7 @@ const tracesRoute = createApmServerRoute({ return await getTopTracesPrimaryStats({ environment, kuery, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -97,12 +100,15 @@ const tracesByIdRoute = createApmServerRoute({ >; linkedChildrenOfSpanCountBySpanId: Record; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { traceId } = params.path; const { start, end } = params.query; - - return getTraceItems(traceId, setup, start, end); + const { config } = setup; + return getTraceItems(traceId, config, apmEventClient, start, end); }, }); @@ -121,8 +127,8 @@ const rootTransactionByTraceIdRoute = createApmServerRoute({ }> => { const { params } = resources; const { traceId } = params.path; - const setup = await setupRequest(resources); - return getRootTransactionByTraceId(traceId, setup); + const apmEventClient = await getApmEventClient(resources); + return getRootTransactionByTraceId(traceId, apmEventClient); }, }); @@ -141,9 +147,9 @@ const transactionByIdRoute = createApmServerRoute({ }> => { const { params } = resources; const { transactionId } = params.path; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); return { - transaction: await getTransaction({ transactionId, setup }), + transaction: await getTransaction({ transactionId, apmEventClient }), }; }, }); @@ -173,11 +179,11 @@ const findTracesRoute = createApmServerRoute({ }> => { const { start, end, environment, query, type } = resources.params.query; - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); return { traceSamples: await getTraceSamplesByQuery({ - setup, + apmEventClient, start, end, environment, diff --git a/x-pack/plugins/apm/server/routes/transactions/breakdown/index.test.ts b/x-pack/plugins/apm/server/routes/transactions/breakdown/index.test.ts index 2df04fcdb0548..96487f7a6be72 100644 --- a/x-pack/plugins/apm/server/routes/transactions/breakdown/index.test.ts +++ b/x-pack/plugins/apm/server/routes/transactions/breakdown/index.test.ts @@ -27,7 +27,6 @@ const mockIndices: ApmIndicesConfig = { function getMockSetup(esResponse: any) { const clientSpy = jest.fn().mockReturnValueOnce(esResponse); return { - apmEventClient: { search: clientSpy } as any, internalClient: { search: clientSpy } as any, config: new Proxy( {}, @@ -39,87 +38,107 @@ function getMockSetup(esResponse: any) { }; } +function getMockApmEventClient(esResponse: any) { + const apmEventClientSpy = jest.fn().mockReturnValueOnce(esResponse); + return { apmEventClient: { search: apmEventClientSpy } as any }; +} + describe('getTransactionBreakdown', () => { - it('returns an empty array if no data is available', async () => { - const response = await getTransactionBreakdown({ - serviceName: 'myServiceName', - transactionType: 'request', - setup: getMockSetup(noDataResponse), - environment: ENVIRONMENT_ALL.value, - kuery: '', - start: 0, - end: 500000, + describe('when no data is available', () => { + const { config } = getMockSetup(noDataResponse); + const { apmEventClient } = getMockApmEventClient(noDataResponse); + it('returns an empty array if no data is available', async () => { + const response = await getTransactionBreakdown({ + serviceName: 'myServiceName', + transactionType: 'request', + config, + apmEventClient, + environment: ENVIRONMENT_ALL.value, + kuery: '', + start: 0, + end: 500000, + }); + + expect(Object.keys(response.timeseries).length).toBe(0); }); - - expect(Object.keys(response.timeseries).length).toBe(0); }); - it('returns a timeseries grouped by type and subtype', async () => { - const response = await getTransactionBreakdown({ - serviceName: 'myServiceName', - transactionType: 'request', - setup: getMockSetup(dataResponse), - environment: ENVIRONMENT_ALL.value, - kuery: '', - start: 0, - end: 500000, + describe('when data is returned', () => { + it('returns a timeseries grouped by type and subtype', async () => { + const { config } = getMockSetup(dataResponse); + const { apmEventClient } = getMockApmEventClient(dataResponse); + const response = await getTransactionBreakdown({ + serviceName: 'myServiceName', + transactionType: 'request', + config, + apmEventClient, + environment: ENVIRONMENT_ALL.value, + kuery: '', + start: 0, + end: 500000, + }); + + const { timeseries } = response; + + expect(timeseries.length).toBe(4); + + const appTimeseries = timeseries[0]; + expect(appTimeseries.title).toBe('app'); + expect(appTimeseries.type).toBe('areaStacked'); + expect(appTimeseries.hideLegend).toBe(false); + + // empty buckets should result in null values for visible types + expect(appTimeseries.data.length).toBe(276); + expect(appTimeseries.data.length).not.toBe(257); + + expect(appTimeseries.data[0].x).toBe(1561102380000); + + expect(appTimeseries.data[0].y).toBeCloseTo(0.8689440187037277); }); - const { timeseries } = response; - - expect(timeseries.length).toBe(4); - - const appTimeseries = timeseries[0]; - expect(appTimeseries.title).toBe('app'); - expect(appTimeseries.type).toBe('areaStacked'); - expect(appTimeseries.hideLegend).toBe(false); - - // empty buckets should result in null values for visible types - expect(appTimeseries.data.length).toBe(276); - expect(appTimeseries.data.length).not.toBe(257); - - expect(appTimeseries.data[0].x).toBe(1561102380000); - - expect(appTimeseries.data[0].y).toBeCloseTo(0.8689440187037277); - }); - - it('should not include more KPIs than MAX_KPIs', async () => { - // @ts-expect-error - constants.MAX_KPIS = 2; - - const response = await getTransactionBreakdown({ - serviceName: 'myServiceName', - transactionType: 'request', - setup: getMockSetup(dataResponse), - environment: ENVIRONMENT_ALL.value, - kuery: '', - start: 0, - end: 500000, + it('should not include more KPIs than MAX_KPIs', async () => { + const { config } = getMockSetup(dataResponse); + const { apmEventClient } = getMockApmEventClient(dataResponse); + // @ts-expect-error + constants.MAX_KPIS = 2; + + const response = await getTransactionBreakdown({ + serviceName: 'myServiceName', + transactionType: 'request', + config, + apmEventClient, + environment: ENVIRONMENT_ALL.value, + kuery: '', + start: 0, + end: 500000, + }); + const { timeseries } = response; + + expect(timeseries.map((serie) => serie.title)).toEqual(['app', 'http']); }); - const { timeseries } = response; - - expect(timeseries.map((serie) => serie.title)).toEqual(['app', 'http']); - }); - - it('fills in gaps for a given timestamp', async () => { - const response = await getTransactionBreakdown({ - serviceName: 'myServiceName', - transactionType: 'request', - setup: getMockSetup(dataResponse), - environment: ENVIRONMENT_ALL.value, - kuery: '', - start: 0, - end: 500000, + it('fills in gaps for a given timestamp', async () => { + const { config } = getMockSetup(dataResponse); + const { apmEventClient } = getMockApmEventClient(dataResponse); + const response = await getTransactionBreakdown({ + serviceName: 'myServiceName', + transactionType: 'request', + config, + apmEventClient, + environment: ENVIRONMENT_ALL.value, + kuery: '', + start: 0, + end: 500000, + }); + + const { timeseries } = response; + + const appTimeseries = timeseries.find((series) => series.title === 'app'); + + // missing values should be 0 if other span types do have data for that timestamp + expect( + (appTimeseries as NonNullable).data[1].y + ).toBe(0); }); - - const { timeseries } = response; - - const appTimeseries = timeseries.find((series) => series.title === 'app'); - - // missing values should be 0 if other span types do have data for that timestamp - expect((appTimeseries as NonNullable).data[1].y).toBe( - 0 - ); }); }); diff --git a/x-pack/plugins/apm/server/routes/transactions/breakdown/index.ts b/x-pack/plugins/apm/server/routes/transactions/breakdown/index.ts index 0c2293ec980c0..5630a4c0c475a 100644 --- a/x-pack/plugins/apm/server/routes/transactions/breakdown/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/breakdown/index.ts @@ -17,16 +17,18 @@ import { TRANSACTION_TYPE, TRANSACTION_NAME, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../lib/helpers/setup_request'; import { environmentQuery } from '../../../../common/utils/environment_query'; import { getMetricsDateHistogramParams } from '../../../lib/helpers/metrics'; import { MAX_KPIS } from './constants'; import { getVizColorForIndex } from '../../../../common/viz_colors'; +import { APMConfig } from '../../..'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransactionBreakdown({ environment, kuery, - setup, + config, + apmEventClient, serviceName, transactionName, transactionType, @@ -35,15 +37,14 @@ export async function getTransactionBreakdown({ }: { environment: string; kuery: string; - setup: Setup; + config: APMConfig; + apmEventClient: APMEventClient; serviceName: string; transactionName?: string; transactionType: string; start: number; end: number; }) { - const { apmEventClient, config } = setup; - const subAggs = { sum_all_self_times: { sum: { diff --git a/x-pack/plugins/apm/server/routes/transactions/get_failed_transaction_rate_periods.ts b/x-pack/plugins/apm/server/routes/transactions/get_failed_transaction_rate_periods.ts index 0538832b6e84c..8a5ec8c83814d 100644 --- a/x-pack/plugins/apm/server/routes/transactions/get_failed_transaction_rate_periods.ts +++ b/x-pack/plugins/apm/server/routes/transactions/get_failed_transaction_rate_periods.ts @@ -4,9 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Setup } from '../../lib/helpers/setup_request'; import { getFailedTransactionRate } from '../../lib/transaction_groups/get_failed_transaction_rate'; import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; +import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; export async function getFailedTransactionRatePeriods({ environment, @@ -14,7 +14,7 @@ export async function getFailedTransactionRatePeriods({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -25,7 +25,7 @@ export async function getFailedTransactionRatePeriods({ serviceName: string; transactionType: string; transactionName?: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; start: number; end: number; @@ -37,7 +37,7 @@ export async function getFailedTransactionRatePeriods({ serviceName, transactionTypes: [transactionType], transactionName, - setup, + apmEventClient, searchAggregatedTransactions, }; diff --git a/x-pack/plugins/apm/server/routes/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/routes/transactions/get_latency_charts/index.ts index 47cc552fe4b54..be534d877d9fd 100644 --- a/x-pack/plugins/apm/server/routes/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/get_latency_charts/index.ts @@ -11,6 +11,7 @@ import { termQuery, } from '@kbn/observability-plugin/server'; import { + FAAS_ID, SERVICE_NAME, TRANSACTION_NAME, TRANSACTION_TYPE, @@ -23,13 +24,13 @@ import { getDurationFieldForTransactions, getProcessorEventForTransactions, } from '../../../lib/helpers/transactions'; -import { Setup } from '../../../lib/helpers/setup_request'; import { getBucketSizeForAggregatedTransactions } from '../../../lib/helpers/get_bucket_size_for_aggregated_transactions'; import { getLatencyAggregation, getLatencyValue, } from '../../../lib/helpers/latency_aggregation_type'; import { getOffsetInMs } from '../../../../common/utils/get_offset_in_ms'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export type LatencyChartsSearchResponse = Awaited< ReturnType @@ -41,27 +42,27 @@ function searchLatency({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, latencyAggregationType, start, end, offset, + serverlessId, }: { environment: string; kuery: string; serviceName: string; transactionType: string | undefined; transactionName: string | undefined; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; start: number; end: number; offset?: string; + serverlessId?: string; }) { - const { apmEventClient } = setup; - const { startWithOffset, endWithOffset } = getOffsetInMs({ start, end, @@ -97,6 +98,7 @@ function searchLatency({ ...kqlQuery(kuery), ...termQuery(TRANSACTION_NAME, transactionName), ...termQuery(TRANSACTION_TYPE, transactionType), + ...termQuery(FAAS_ID, serverlessId), ], }, }, @@ -127,24 +129,26 @@ export async function getLatencyTimeseries({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, latencyAggregationType, start, end, offset, + serverlessId, }: { environment: string; kuery: string; serviceName: string; transactionType?: string; transactionName?: string; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; start: number; end: number; offset?: string; + serverlessId?: string; }) { const response = await searchLatency({ environment, @@ -152,12 +156,13 @@ export async function getLatencyTimeseries({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, latencyAggregationType, start, end, offset, + serverlessId, }); if (!response.aggregations) { @@ -185,7 +190,7 @@ export async function getLatencyPeriods({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, latencyAggregationType, kuery, @@ -197,7 +202,7 @@ export async function getLatencyPeriods({ serviceName: string; transactionType: string | undefined; transactionName: string | undefined; - setup: Setup; + apmEventClient: APMEventClient; searchAggregatedTransactions: boolean; latencyAggregationType: LatencyAggregationType; kuery: string; @@ -210,7 +215,7 @@ export async function getLatencyPeriods({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, kuery, environment, diff --git a/x-pack/plugins/apm/server/routes/transactions/get_transaction/index.ts b/x-pack/plugins/apm/server/routes/transactions/get_transaction/index.ts index f3eab3707e1ed..9ca077744899e 100644 --- a/x-pack/plugins/apm/server/routes/transactions/get_transaction/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/get_transaction/index.ts @@ -11,24 +11,22 @@ import { TRACE_ID, TRANSACTION_ID, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../lib/helpers/setup_request'; import { asMutableArray } from '../../../../common/utils/as_mutable_array'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getTransaction({ transactionId, traceId, - setup, + apmEventClient, start, end, }: { transactionId: string; traceId?: string; - setup: Setup; + apmEventClient: APMEventClient; start?: number; end?: number; }) { - const { apmEventClient } = setup; - const resp = await apmEventClient.search('get_transaction', { apm: { events: [ProcessorEvent.transaction], diff --git a/x-pack/plugins/apm/server/routes/transactions/get_transaction_by_trace/index.ts b/x-pack/plugins/apm/server/routes/transactions/get_transaction_by_trace/index.ts index ca3b3bba12307..0f27d37c2b0ab 100644 --- a/x-pack/plugins/apm/server/routes/transactions/get_transaction_by_trace/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/get_transaction_by_trace/index.ts @@ -10,14 +10,12 @@ import { TRACE_ID, PARENT_ID, } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; export async function getRootTransactionByTraceId( traceId: string, - setup: Setup + apmEventClient: APMEventClient ) { - const { apmEventClient } = setup; - const params = { apm: { events: [ProcessorEvent.transaction as const], diff --git a/x-pack/plugins/apm/server/routes/transactions/queries.test.ts b/x-pack/plugins/apm/server/routes/transactions/queries.test.ts index 4770dce0a1cb2..be11546ee20a1 100644 --- a/x-pack/plugins/apm/server/routes/transactions/queries.test.ts +++ b/x-pack/plugins/apm/server/routes/transactions/queries.test.ts @@ -22,11 +22,12 @@ describe('transaction queries', () => { }); it('fetches breakdown data for transactions', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTransactionBreakdown({ serviceName: 'foo', transactionType: 'bar', - setup, + config: setup.config, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -38,12 +39,13 @@ describe('transaction queries', () => { }); it('fetches breakdown data for transactions for a transaction name', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTransactionBreakdown({ serviceName: 'foo', transactionType: 'bar', transactionName: 'baz', - setup, + config: setup.config, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -55,14 +57,14 @@ describe('transaction queries', () => { }); it('fetches transaction trace samples', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTraceSamples({ serviceName: 'foo', transactionName: 'bar', transactionType: 'baz', traceId: 'qux', transactionId: 'quz', - setup, + apmEventClient, environment: ENVIRONMENT_ALL.value, kuery: '', start: 0, @@ -74,11 +76,11 @@ describe('transaction queries', () => { }); it('fetches a transaction', async () => { - mock = await inspectSearchParams((setup) => + mock = await inspectSearchParams((setup, apmEventClient) => getTransaction({ transactionId: 'foo', traceId: 'bar', - setup, + apmEventClient, start: 0, end: 50000, }) diff --git a/x-pack/plugins/apm/server/routes/transactions/route.ts b/x-pack/plugins/apm/server/routes/transactions/route.ts index 3bc4dbfe7aae7..b862fdd37cb5b 100644 --- a/x-pack/plugins/apm/server/routes/transactions/route.ts +++ b/x-pack/plugins/apm/server/routes/transactions/route.ts @@ -23,6 +23,7 @@ import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { environmentRt, kueryRt, rangeRt } from '../default_api_types'; import { offsetRt } from '../../../common/comparison_rt'; import { getTraceSamples } from './trace_samples'; +import { getApmEventClient } from '../../lib/helpers/get_apm_event_client'; const transactionGroupsMainStatisticsRoute = createApmServerRoute({ endpoint: @@ -57,7 +58,10 @@ const transactionGroupsMainStatisticsRoute = createApmServerRoute({ bucketSize: number; }> => { const { params } = resources; - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { path: { serviceName }, query: { @@ -69,9 +73,10 @@ const transactionGroupsMainStatisticsRoute = createApmServerRoute({ end, }, } = params; - + const { config } = setup; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + apmEventClient, + config, kuery, start, end, @@ -80,7 +85,8 @@ const transactionGroupsMainStatisticsRoute = createApmServerRoute({ return getServiceTransactionGroups({ environment, kuery, - setup, + config, + apmEventClient, serviceName, searchAggregatedTransactions, transactionType, @@ -139,7 +145,10 @@ const transactionGroupsDetailedStatisticsRoute = createApmServerRoute({ impact: number; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { @@ -158,7 +167,8 @@ const transactionGroupsDetailedStatisticsRoute = createApmServerRoute({ } = params; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -167,7 +177,7 @@ const transactionGroupsDetailedStatisticsRoute = createApmServerRoute({ return await getServiceTransactionGroupDetailedStatisticsPeriods({ environment, kuery, - setup, + apmEventClient, serviceName, transactionNames, searchAggregatedTransactions, @@ -221,7 +231,10 @@ const transactionLatencyChartsRoute = createApmServerRoute({ overallAvgDuration: null; }; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params, logger } = resources; const { serviceName } = params.path; @@ -237,7 +250,8 @@ const transactionLatencyChartsRoute = createApmServerRoute({ } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -249,7 +263,7 @@ const transactionLatencyChartsRoute = createApmServerRoute({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, logger, start, @@ -298,7 +312,7 @@ const transactionTraceSamplesRoute = createApmServerRoute({ ): Promise<{ traceSamples: Array<{ transactionId: string; traceId: string }>; }> => { - const setup = await setupRequest(resources); + const apmEventClient = await getApmEventClient(resources); const { params } = resources; const { serviceName } = params.path; const { @@ -324,7 +338,7 @@ const transactionTraceSamplesRoute = createApmServerRoute({ traceId, sampleRangeFrom, sampleRangeTo, - setup, + apmEventClient, start, end, }); @@ -359,7 +373,10 @@ const transactionChartsBreakdownRoute = createApmServerRoute({ legendValue: string; }>; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; @@ -372,7 +389,8 @@ const transactionChartsBreakdownRoute = createApmServerRoute({ serviceName, transactionName, transactionType, - setup, + config: setup.config, + apmEventClient, start, end, }); @@ -416,7 +434,10 @@ const transactionChartsErrorRateRoute = createApmServerRoute({ average: null; }; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; @@ -431,7 +452,8 @@ const transactionChartsErrorRateRoute = createApmServerRoute({ } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -443,7 +465,7 @@ const transactionChartsErrorRateRoute = createApmServerRoute({ serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -490,7 +512,10 @@ const transactionChartsColdstartRateRoute = createApmServerRoute({ average: null; }; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; @@ -498,7 +523,8 @@ const transactionChartsColdstartRateRoute = createApmServerRoute({ params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -509,7 +535,7 @@ const transactionChartsColdstartRateRoute = createApmServerRoute({ kuery, serviceName, transactionType, - setup, + apmEventClient, searchAggregatedTransactions, start, end, @@ -557,7 +583,10 @@ const transactionChartsColdstartRateByTransactionNameRoute = average: null; }; }> => { - const setup = await setupRequest(resources); + const [setup, apmEventClient] = await Promise.all([ + setupRequest(resources), + getApmEventClient(resources), + ]); const { params } = resources; const { serviceName } = params.path; @@ -572,7 +601,8 @@ const transactionChartsColdstartRateByTransactionNameRoute = } = params.query; const searchAggregatedTransactions = await getSearchTransactionsEvents({ - ...setup, + config: setup.config, + apmEventClient, kuery, start, end, @@ -584,7 +614,7 @@ const transactionChartsColdstartRateByTransactionNameRoute = serviceName, transactionType, transactionName, - setup, + apmEventClient, searchAggregatedTransactions, start, end, diff --git a/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts b/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts index 572fa1a1c6d11..9fe3e7ff79335 100644 --- a/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts +++ b/x-pack/plugins/apm/server/routes/transactions/trace_samples/index.ts @@ -18,7 +18,7 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { environmentQuery } from '../../../../common/utils/environment_query'; -import { Setup } from '../../../lib/helpers/setup_request'; +import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client'; const TRACE_SAMPLES_SIZE = 500; @@ -32,7 +32,7 @@ export async function getTraceSamples({ traceId, sampleRangeFrom, sampleRangeTo, - setup, + apmEventClient, start, end, }: { @@ -45,13 +45,11 @@ export async function getTraceSamples({ traceId: string; sampleRangeFrom?: number; sampleRangeTo?: number; - setup: Setup; + apmEventClient: APMEventClient; start: number; end: number; }) { return withApmSpan('get_trace_samples', async () => { - const { apmEventClient } = setup; - const commonFilters = [ { term: { [SERVICE_NAME]: serviceName } }, { term: { [TRANSACTION_TYPE]: transactionType } }, diff --git a/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts b/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts index f3d15ee6db21e..f4ea0629f848b 100644 --- a/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts +++ b/x-pack/plugins/apm/server/test_helpers/create_apm_users/authentication.ts @@ -15,7 +15,7 @@ export enum ApmUsername { apmReadUserWithoutMlAccess = 'apm_read_user_without_ml_access', apmManageOwnAgentKeys = 'apm_manage_own_agent_keys', apmManageOwnAndCreateAgentKeys = 'apm_manage_own_and_create_agent_keys', - apmMonitorIndices = 'apm_monitor_indices', + apmMonitorClusterAndIndices = 'apm_monitor_cluster_and_indices', } export enum ApmCustomRolename { @@ -23,7 +23,7 @@ export enum ApmCustomRolename { apmAnnotationsWriteUser = 'apm_annotations_write_user', apmManageOwnAgentKeys = 'apm_manage_own_agent_keys', apmManageOwnAndCreateAgentKeys = 'apm_manage_own_and_create_agent_keys', - apmMonitorIndices = 'apm_monitor_indices', + apmMonitorClusterAndIndices = 'apm_monitor_cluster_and_indices', } export const customRoles = { @@ -77,7 +77,7 @@ export const customRoles = { }, ], }, - [ApmCustomRolename.apmMonitorIndices]: { + [ApmCustomRolename.apmMonitorClusterAndIndices]: { elasticsearch: { indices: [ { @@ -85,6 +85,7 @@ export const customRoles = { privileges: ['monitor'], }, ], + cluster: ['monitor'], }, }, }; @@ -118,9 +119,9 @@ export const users: Record< ApmCustomRolename.apmManageOwnAndCreateAgentKeys, ], }, - [ApmUsername.apmMonitorIndices]: { + [ApmUsername.apmMonitorClusterAndIndices]: { builtInRoleNames: ['viewer'], - customRoleNames: [ApmCustomRolename.apmMonitorIndices], + customRoleNames: [ApmCustomRolename.apmMonitorClusterAndIndices], }, }; diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx index c39e2b78d07c8..db69e4d78ea4f 100644 --- a/x-pack/plugins/apm/server/utils/test_helpers.tsx +++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx @@ -7,6 +7,7 @@ import type { ESSearchRequest, ESSearchResponse } from '@kbn/es-types'; import { APMConfig } from '..'; +import { APMEventClient } from '../lib/helpers/create_es_client/create_apm_event_client'; import { ApmIndicesConfig } from '../routes/settings/apm_indices/get_apm_indices'; interface Options { @@ -17,14 +18,16 @@ interface Options { } interface MockSetup { - apmEventClient: any; internalClient: any; config: APMConfig; indices: ApmIndicesConfig; } export async function inspectSearchParams( - fn: (mockSetup: MockSetup) => Promise, + fn: ( + mockSetup: MockSetup, + mockApmEventClient: APMEventClient + ) => Promise, options: Options = {} ) { const spy = jest.fn().mockImplementation(async (request) => { @@ -43,7 +46,7 @@ export async function inspectSearchParams( let response; let error; - + const mockApmEventClient = { search: spy } as any; const mockApmIndices: { [Property in keyof APMConfig['indices']]: string; } = { @@ -55,7 +58,6 @@ export async function inspectSearchParams( metric: 'myIndex', }; const mockSetup = { - apmEventClient: { search: spy } as any, internalClient: { search: spy } as any, config: new Proxy( {}, @@ -90,7 +92,7 @@ export async function inspectSearchParams( }, }; try { - response = await fn(mockSetup); + response = await fn(mockSetup, mockApmEventClient); } catch (err) { error = err; // we're only extracting the search params diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/escount.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/escount.ts index 7bdb13cff93c0..b324d2f11a0a8 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/escount.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/escount.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { lastValueFrom } from 'rxjs'; + import { ExpressionFunctionDefinition, ExpressionValueFilter, @@ -12,10 +14,8 @@ import { // @ts-expect-error untyped local import { buildESRequest } from '../../../common/lib/request/build_es_request'; - -import { searchService } from '../../../public/services'; - import { getFunctionHelp } from '../../../i18n'; +import { searchService } from '../../../public/services'; interface Arguments { index: string | null; @@ -46,6 +46,7 @@ export function escount(): ExpressionFunctionDefinition< }, index: { types: ['string'], + aliases: ['dataView'], default: '_all', help: argHelp.index, }, @@ -83,12 +84,9 @@ export function escount(): ExpressionFunctionDefinition< }, }; - return search - .search(req) - .toPromise() - .then((resp: any) => { - return resp.rawResponse.hits.total; - }); + return lastValueFrom(search.search(req)).then((resp: any) => { + return resp.rawResponse.hits.total; + }); }, }; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts index 83225fcafb8d8..763b1839ec3b8 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/esdocs.ts @@ -70,6 +70,7 @@ export function esdocs(): ExpressionFunctionDefinition< }, index: { types: ['string'], + aliases: ['dataView'], default: '_all', help: argHelp.index, }, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js index 150b7c0616887..ae0af7b2297f5 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/datasources/esdocs.js @@ -20,7 +20,7 @@ import { import { getSimpleArg, setSimpleArg } from '../../../public/lib/arg_helpers'; import { ESFieldsSelect } from '../../../public/components/es_fields_select'; import { ESFieldSelect } from '../../../public/components/es_field_select'; -import { ESIndexSelect } from '../../../public/components/es_index_select'; +import { ESDataViewSelect } from '../../../public/components/es_data_view_select'; import { templateFromReactComponent } from '../../../public/lib/template_from_react_component'; import { DataSourceStrings, LUCENE_QUERY_URL } from '../../../i18n'; @@ -29,6 +29,7 @@ const { ESDocs: strings } = DataSourceStrings; const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => { const setArg = useCallback( (name, value) => { + console.log({ name, value }); updateArgs && updateArgs({ ...args, @@ -94,7 +95,7 @@ const EsdocsDatasource = ({ args, updateArgs, defaultIndex }) => { helpText={strings.getIndexLabel()} display="rowCompressed" > - setArg('index', index)} /> + setArg('index', index)} /> i18n.translate('xpack.canvas.uis.dataSources.esdocs.indexTitle', { - defaultMessage: 'Index', + defaultMessage: 'Data view', }), getIndexLabel: () => i18n.translate('xpack.canvas.uis.dataSources.esdocs.indexLabel', { - defaultMessage: 'Enter an index name or select a data view', + defaultMessage: 'Select a data view or enter an index name.', }), getQueryTitle: () => i18n.translate('xpack.canvas.uis.dataSources.esdocs.queryTitle', { diff --git a/x-pack/plugins/canvas/kibana.json b/x-pack/plugins/canvas/kibana.json index 48f4d9a6b0d58..f63c3522a8df7 100644 --- a/x-pack/plugins/canvas/kibana.json +++ b/x-pack/plugins/canvas/kibana.json @@ -14,6 +14,7 @@ "bfetch", "charts", "data", + "dataViews", "embeddable", "expressionError", "expressionImage", diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js index 8e2176b845bfa..1a357b6722c71 100644 --- a/x-pack/plugins/canvas/public/components/datasource/datasource_component.js +++ b/x-pack/plugins/canvas/public/components/datasource/datasource_component.js @@ -20,7 +20,7 @@ import { import { isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { getDefaultIndex } from '../../lib/es_service'; +import { pluginServices } from '../../services'; import { DatasourceSelector } from './datasource_selector'; import { DatasourcePreview } from './datasource_preview'; @@ -67,7 +67,12 @@ export class DatasourceComponent extends PureComponent { state = { defaultIndex: '' }; componentDidMount() { - getDefaultIndex().then((defaultIndex) => this.setState({ defaultIndex })); + pluginServices + .getServices() + .dataViews.getDefaultDataView() + .then((defaultDataView) => { + this.setState({ defaultIndex: defaultDataView.title }); + }); } componentDidUpdate(prevProps) { diff --git a/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.component.tsx b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx similarity index 50% rename from x-pack/plugins/canvas/public/components/es_index_select/es_index_select.component.tsx rename to x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx index 636b19092123a..da8b4cda4c17c 100644 --- a/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.component.tsx +++ b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.component.tsx @@ -7,37 +7,47 @@ import React, { FocusEventHandler } from 'react'; import { EuiComboBox } from '@elastic/eui'; +import { DataView } from '@kbn/data-views-plugin/common'; -export interface ESIndexSelectProps { +type DataViewOption = Pick; + +export interface ESDataViewSelectProps { loading: boolean; value: string; - indices: string[]; - onChange: (index: string) => void; + dataViews: DataViewOption[]; + onChange: (string: string) => void; onBlur: FocusEventHandler | undefined; onFocus: FocusEventHandler | undefined; } const defaultIndex = '_all'; +const defaultOption = { value: defaultIndex, label: defaultIndex }; -export const ESIndexSelect: React.FunctionComponent = ({ +export const ESDataViewSelect: React.FunctionComponent = ({ value = defaultIndex, loading, - indices, + dataViews, onChange, onFocus, onBlur, }) => { - const selectedOption = value !== defaultIndex ? [{ label: value }] : []; - const options = indices.map((index) => ({ label: index })); + const selectedDataView = dataViews.find((view) => value === view.title) as DataView; + + const selectedOption = selectedDataView + ? { value: selectedDataView.title, label: selectedDataView.name } + : { value, label: value }; + const options = dataViews.map(({ name, title }) => ({ value: title, label: name })); return ( onChange(index?.label ?? defaultIndex)} + selectedOptions={[selectedOption]} + onChange={([view]) => { + onChange(view.value || defaultOption.value); + }} onSearchChange={(searchValue) => { // resets input when user starts typing if (searchValue) { - onChange(defaultIndex); + onChange(defaultOption.value); } }} onBlur={onBlur} @@ -46,7 +56,7 @@ export const ESIndexSelect: React.FunctionComponent = ({ options={options} singleSelection={{ asPlainText: true }} isClearable={false} - onCreateOption={(input) => onChange(input || defaultIndex)} + onCreateOption={(input) => onChange(input || defaultOption.value)} compressed /> ); diff --git a/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.tsx b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.tsx new file mode 100644 index 0000000000000..83e21b8ba2e6f --- /dev/null +++ b/x-pack/plugins/canvas/public/components/es_data_view_select/es_data_view_select.tsx @@ -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 { DataView } from '@kbn/data-views-plugin/common'; +import { sortBy } from 'lodash'; +import React, { FC, useRef, useState } from 'react'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; +import { useDataViewsService } from '../../services'; +import { + ESDataViewSelect as Component, + ESDataViewSelectProps as Props, +} from './es_data_view_select.component'; + +type ESDataViewSelectProps = Omit; + +export const ESDataViewSelect: FC = (props) => { + const { value, onChange } = props; + + const [dataViews, setDataViews] = useState>>([]); + const [loading, setLoading] = useState(true); + const mounted = useRef(true); + const { getDataViews } = useDataViewsService(); + + useEffectOnce(() => { + getDataViews().then((newDataViews) => { + if (!mounted.current) { + return; + } + + if (!newDataViews) { + newDataViews = []; + } + + setLoading(false); + setDataViews(sortBy(newDataViews, 'name')); + if (!value && newDataViews.length) { + onChange(newDataViews[0].title); + } + }); + + return () => { + mounted.current = false; + }; + }); + + return ; +}; diff --git a/x-pack/plugins/canvas/public/components/es_data_view_select/index.tsx b/x-pack/plugins/canvas/public/components/es_data_view_select/index.tsx new file mode 100644 index 0000000000000..870a6b3aff1aa --- /dev/null +++ b/x-pack/plugins/canvas/public/components/es_data_view_select/index.tsx @@ -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 { ESDataViewSelect } from './es_data_view_select'; +export { ESDataViewSelect as ESDataViewSelectComponent } from './es_data_view_select.component'; diff --git a/x-pack/plugins/canvas/public/components/es_field_select/index.tsx b/x-pack/plugins/canvas/public/components/es_field_select/index.tsx index 8c0baea681731..653eec22d77d9 100644 --- a/x-pack/plugins/canvas/public/components/es_field_select/index.tsx +++ b/x-pack/plugins/canvas/public/components/es_field_select/index.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useEffect, useRef } from 'react'; -import { getFields } from '../../lib/es_service'; +import { useDataViewsService } from '../../services'; import { ESFieldSelect as Component, ESFieldSelectProps as Props } from './es_field_select'; type ESFieldSelectProps = Omit; @@ -15,15 +15,17 @@ export const ESFieldSelect: React.FunctionComponent = (props const { index, value, onChange } = props; const [fields, setFields] = useState([]); const loadingFields = useRef(false); + const { getFields } = useDataViewsService(); useEffect(() => { loadingFields.current = true; + getFields(index) .then((newFields) => setFields(newFields || [])) .finally(() => { loadingFields.current = false; }); - }, [index]); + }, [index, getFields]); useEffect(() => { if (!loadingFields.current && value && !fields.includes(value)) { diff --git a/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx b/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx index 2f94b154edab2..c929203f9e094 100644 --- a/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx +++ b/x-pack/plugins/canvas/public/components/es_fields_select/es_fields_select.tsx @@ -8,7 +8,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { isEqual } from 'lodash'; import usePrevious from 'react-use/lib/usePrevious'; -import { getFields } from '../../lib/es_service'; +import { useDataViewsService } from '../../services'; import { ESFieldsSelect as Component, ESFieldsSelectProps as Props, @@ -21,6 +21,7 @@ export const ESFieldsSelect: React.FunctionComponent = (pro const [fields, setFields] = useState([]); const prevIndex = usePrevious(index); const mounted = useRef(true); + const { getFields } = useDataViewsService(); useEffect(() => { if (prevIndex !== index) { @@ -36,7 +37,7 @@ export const ESFieldsSelect: React.FunctionComponent = (pro } }); } - }, [fields, index, onChange, prevIndex, selected]); + }, [fields, index, onChange, prevIndex, selected, getFields]); useEffect( () => () => { diff --git a/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.tsx b/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.tsx deleted file mode 100644 index 7d2e87902d2d1..0000000000000 --- a/x-pack/plugins/canvas/public/components/es_index_select/es_index_select.tsx +++ /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 React, { useRef, useState } from 'react'; -import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { getIndices } from '../../lib/es_service'; -import { - ESIndexSelect as Component, - ESIndexSelectProps as Props, -} from './es_index_select.component'; - -type ESIndexSelectProps = Omit; - -export const ESIndexSelect: React.FunctionComponent = (props) => { - const { value, onChange } = props; - - const [indices, setIndices] = useState([]); - const [loading, setLoading] = useState(true); - const mounted = useRef(true); - - useEffectOnce(() => { - getIndices().then((newIndices) => { - if (!mounted.current) { - return; - } - - if (!newIndices) { - newIndices = []; - } - - setLoading(false); - setIndices(newIndices.sort()); - if (!value && newIndices.length) { - onChange(newIndices[0]); - } - }); - - return () => { - mounted.current = false; - }; - }); - - return ; -}; diff --git a/x-pack/plugins/canvas/public/lib/es_service.ts b/x-pack/plugins/canvas/public/lib/es_service.ts deleted file mode 100644 index 9d558243c9421..0000000000000 --- a/x-pack/plugins/canvas/public/lib/es_service.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. - */ - -// TODO - clint: convert to service abstraction - -import { API_ROUTE } from '../../common/lib/constants'; -import { fetch } from '../../common/lib/fetch'; -import { ErrorStrings } from '../../i18n'; -import { pluginServices } from '../services'; - -const { esService: strings } = ErrorStrings; - -const getApiPath = function () { - const platformService = pluginServices.getServices().platform; - const basePath = platformService.getBasePath(); - return basePath + API_ROUTE; -}; - -const getSavedObjectsClient = function () { - const platformService = pluginServices.getServices().platform; - return platformService.getSavedObjectsClient(); -}; - -const getAdvancedSettings = function () { - const platformService = pluginServices.getServices().platform; - return platformService.getUISettings(); -}; - -export const getFields = (index = '_all') => { - return fetch - .get(`${getApiPath()}/es_fields?index=${index}`) - .then(({ data: mapping }: { data: object }) => - Object.keys(mapping) - .filter((field) => !field.startsWith('_')) // filters out meta fields - .sort() - ) - .catch((err: Error) => { - const notifyService = pluginServices.getServices().notify; - notifyService.error(err, { - title: strings.getFieldsFetchErrorMessage(index), - }); - }); -}; - -export const getIndices = () => - getSavedObjectsClient() - .find<{ title: string }>({ - type: 'index-pattern', - fields: ['title'], - searchFields: ['title'], - perPage: 1000, - }) - .then((resp) => { - return resp.savedObjects.map((savedObject) => { - return savedObject.attributes.title; - }); - }) - .catch((err: Error) => { - const notifyService = pluginServices.getServices().notify; - notifyService.error(err, { title: strings.getIndicesFetchErrorMessage() }); - }); - -export const getDefaultIndex = () => { - const defaultIndexId = getAdvancedSettings().get('defaultIndex'); - - return defaultIndexId - ? getSavedObjectsClient() - .get<{ title: string }>('index-pattern', defaultIndexId) - .then((defaultIndex) => defaultIndex.attributes.title) - .catch((err) => { - const notifyService = pluginServices.getServices().notify; - notifyService.error(err, { title: strings.getDefaultIndexFetchErrorMessage() }); - }) - : Promise.resolve(''); -}; diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx index 085a9093e8b8a..acbd425e1d245 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_autoplay_helper.test.tsx @@ -25,7 +25,7 @@ const getContextWrapper: (context: WorkpadRoutingContextType) => FC = {children}; describe('useAutoplayHelper', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); test('starts the timer when fullscreen and autoplay is on', () => { const context = getMockedContext({ isFullscreen: true, @@ -47,7 +47,7 @@ describe('useAutoplayHelper', () => { const { rerender } = renderHook(useAutoplayHelper, { wrapper: getContextWrapper(context) }); - jest.runTimersToTime(context.autoplayInterval - 1); + jest.advanceTimersByTime(context.autoplayInterval - 1); context.isAutoplayPaused = true; diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx index d502e634ede04..ac64f509b56b4 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_refresh_helper.test.tsx @@ -37,7 +37,7 @@ const getContextWrapper: (context: WorkpadRoutingContextType) => FC = describe('useRefreshHelper', () => { beforeEach(() => { jest.resetAllMocks(); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); test('starts a timer to refresh', () => { @@ -73,7 +73,7 @@ describe('useRefreshHelper', () => { mockGetState.mockReturnValue(state); const { rerender } = renderHook(useRefreshHelper, { wrapper: getContextWrapper(context) }); - jest.runTimersToTime(context.refreshInterval - 1); + jest.advanceTimersByTime(context.refreshInterval - 1); expect(mockDispatch).not.toHaveBeenCalledWith(refreshAction); state.transient.inFlight = true; diff --git a/x-pack/plugins/canvas/public/services/data_views.ts b/x-pack/plugins/canvas/public/services/data_views.ts new file mode 100644 index 0000000000000..86faa87bfaa59 --- /dev/null +++ b/x-pack/plugins/canvas/public/services/data_views.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 { DataView } from '@kbn/data-views-plugin/common'; + +export interface CanvasDataViewsService { + getFields: (index: string) => Promise; + getDataViews: () => Promise>>; + getDefaultDataView: () => Promise | undefined>; +} diff --git a/x-pack/plugins/canvas/public/services/index.ts b/x-pack/plugins/canvas/public/services/index.ts index 2c01378851ee5..a6ebc865fbd3f 100644 --- a/x-pack/plugins/canvas/public/services/index.ts +++ b/x-pack/plugins/canvas/public/services/index.ts @@ -10,6 +10,7 @@ export * from './legacy'; import { PluginServices } from '@kbn/presentation-util-plugin/public'; import { CanvasCustomElementService } from './custom_element'; +import { CanvasDataViewsService } from './data_views'; import { CanvasEmbeddablesService } from './embeddables'; import { CanvasExpressionsService } from './expressions'; import { CanvasFiltersService } from './filters'; @@ -23,6 +24,7 @@ import { CanvasWorkpadService } from './workpad'; export interface CanvasPluginServices { customElement: CanvasCustomElementService; + dataViews: CanvasDataViewsService; embeddables: CanvasEmbeddablesService; expressions: CanvasExpressionsService; filters: CanvasFiltersService; @@ -39,6 +41,7 @@ export const pluginServices = new PluginServices(); export const useCustomElementService = () => (() => pluginServices.getHooks().customElement.useService())(); +export const useDataViewsService = () => (() => pluginServices.getHooks().dataViews.useService())(); export const useEmbeddablesService = () => (() => pluginServices.getHooks().embeddables.useService())(); export const useExpressionsService = () => diff --git a/x-pack/plugins/canvas/public/services/kibana/data_views.ts b/x-pack/plugins/canvas/public/services/kibana/data_views.ts new file mode 100644 index 0000000000000..99e95c2e9dc4f --- /dev/null +++ b/x-pack/plugins/canvas/public/services/kibana/data_views.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DataView } from '@kbn/data-views-plugin/public'; +import { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { ErrorStrings } from '../../../i18n'; +import { CanvasStartDeps } from '../../plugin'; +import { CanvasDataViewsService } from '../data_views'; +import { CanvasNotifyService } from '../notify'; + +const { esService: strings } = ErrorStrings; + +export type DataViewsServiceFactory = KibanaPluginServiceFactory< + CanvasDataViewsService, + CanvasStartDeps, + { + notify: CanvasNotifyService; + } +>; + +export const dataViewsServiceFactory: DataViewsServiceFactory = ({ startPlugins }, { notify }) => ({ + getDataViews: async () => { + try { + const dataViews = await startPlugins.data.dataViews.getIdsWithTitle(); + return dataViews.map(({ id, name, title }) => ({ id, name, title } as DataView)); + } catch (e) { + notify.error(e, { title: strings.getIndicesFetchErrorMessage() }); + } + + return []; + }, + getFields: async (dataViewTitle: string) => { + const dataView = await startPlugins.data.dataViews.create({ title: dataViewTitle }); + + return dataView.fields + .filter((field) => !field.name.startsWith('_')) + .map((field) => field.name); + }, + getDefaultDataView: async () => { + const dataView = await startPlugins.data.dataViews.getDefaultDataView(); + + return dataView + ? { id: dataView.id, name: dataView.name, title: dataView.getIndexPattern() } + : undefined; + }, +}); diff --git a/x-pack/plugins/canvas/public/services/kibana/index.ts b/x-pack/plugins/canvas/public/services/kibana/index.ts index cd5e704296405..0d4dd20a52fef 100644 --- a/x-pack/plugins/canvas/public/services/kibana/index.ts +++ b/x-pack/plugins/canvas/public/services/kibana/index.ts @@ -15,6 +15,7 @@ import { import { CanvasPluginServices } from '..'; import { CanvasStartDeps } from '../../plugin'; import { customElementServiceFactory } from './custom_element'; +import { dataViewsServiceFactory } from './data_views'; import { embeddablesServiceFactory } from './embeddables'; import { expressionsServiceFactory } from './expressions'; import { labsServiceFactory } from './labs'; @@ -27,6 +28,7 @@ import { workpadServiceFactory } from './workpad'; import { filtersServiceFactory } from './filters'; export { customElementServiceFactory } from './custom_element'; +export { dataViewsServiceFactory } from './data_views'; export { embeddablesServiceFactory } from './embeddables'; export { expressionsServiceFactory } from './expressions'; export { filtersServiceFactory } from './filters'; @@ -42,6 +44,7 @@ export const pluginServiceProviders: PluginServiceProviders< KibanaPluginServiceParams > = { customElement: new PluginServiceProvider(customElementServiceFactory), + dataViews: new PluginServiceProvider(dataViewsServiceFactory, ['notify']), embeddables: new PluginServiceProvider(embeddablesServiceFactory), expressions: new PluginServiceProvider(expressionsServiceFactory, ['filters', 'notify']), filters: new PluginServiceProvider(filtersServiceFactory), diff --git a/x-pack/plugins/canvas/public/services/stubs/data_views.ts b/x-pack/plugins/canvas/public/services/stubs/data_views.ts new file mode 100644 index 0000000000000..1b1227dba4d3f --- /dev/null +++ b/x-pack/plugins/canvas/public/services/stubs/data_views.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 { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { CanvasDataViewsService } from '../data_views'; + +type DataViewsServiceFactory = PluginServiceFactory; + +export const dataViewsServiceFactory: DataViewsServiceFactory = () => ({ + getDataViews: () => + Promise.resolve([ + { id: 'dataview1', title: 'dataview1', name: 'Data view 1' }, + { id: 'dataview2', title: 'dataview2', name: 'Data view 2' }, + ]), + getFields: () => Promise.resolve(['field1', 'field2']), + getDefaultDataView: () => + Promise.resolve({ + id: 'defaultDataViewId', + title: 'defaultDataView', + name: 'Default data view', + }), +}); diff --git a/x-pack/plugins/canvas/public/services/stubs/index.ts b/x-pack/plugins/canvas/public/services/stubs/index.ts index bb47f0506ce89..7a6abd470c004 100644 --- a/x-pack/plugins/canvas/public/services/stubs/index.ts +++ b/x-pack/plugins/canvas/public/services/stubs/index.ts @@ -15,6 +15,7 @@ import { import { CanvasPluginServices } from '..'; import { customElementServiceFactory } from './custom_element'; +import { dataViewsServiceFactory } from './data_views'; import { embeddablesServiceFactory } from './embeddables'; import { expressionsServiceFactory } from './expressions'; import { labsServiceFactory } from './labs'; @@ -27,6 +28,7 @@ import { workpadServiceFactory } from './workpad'; import { filtersServiceFactory } from './filters'; export { customElementServiceFactory } from './custom_element'; +export { dataViewsServiceFactory } from './data_views'; export { expressionsServiceFactory } from './expressions'; export { filtersServiceFactory } from './filters'; export { labsServiceFactory } from './labs'; @@ -39,6 +41,7 @@ export { workpadServiceFactory } from './workpad'; export const pluginServiceProviders: PluginServiceProviders = { customElement: new PluginServiceProvider(customElementServiceFactory), + dataViews: new PluginServiceProvider(dataViewsServiceFactory), embeddables: new PluginServiceProvider(embeddablesServiceFactory), expressions: new PluginServiceProvider(expressionsServiceFactory, ['filters', 'notify']), filters: new PluginServiceProvider(filtersServiceFactory), diff --git a/x-pack/plugins/canvas/storybook/canvas_webpack.ts b/x-pack/plugins/canvas/storybook/canvas_webpack.ts index 502e175b91b06..946b6c5b78cec 100644 --- a/x-pack/plugins/canvas/storybook/canvas_webpack.ts +++ b/x-pack/plugins/canvas/storybook/canvas_webpack.ts @@ -56,10 +56,6 @@ export const canvasWebpack = { resolve: { alias: { 'src/plugins': resolve(KIBANA_ROOT, 'src/plugins'), - '../../lib/es_service': resolve( - KIBANA_ROOT, - 'x-pack/plugins/canvas/storybook/__mocks__/es_service.ts' - ), }, }, }; diff --git a/x-pack/plugins/canvas/storybook/storyshots.test.tsx b/x-pack/plugins/canvas/storybook/storyshots.skipped_test.tsx similarity index 96% rename from x-pack/plugins/canvas/storybook/storyshots.test.tsx rename to x-pack/plugins/canvas/storybook/storyshots.skipped_test.tsx index 39dde64283ba3..1088113124578 100644 --- a/x-pack/plugins/canvas/storybook/storyshots.test.tsx +++ b/x-pack/plugins/canvas/storybook/storyshots.skipped_test.tsx @@ -5,6 +5,9 @@ * 2.0. */ +// This file is skipped +// @storybook/addon-storyshots is not supported in Jest 27+ https://github.com/storybookjs/storybook/issues/15916 + import fs from 'fs'; import { ReactChildren, createElement } from 'react'; import path from 'path'; @@ -85,11 +88,6 @@ if (!fs.existsSync(cssDir)) { fs.mkdirSync(cssDir, { recursive: true }); } -// Mock index for datasource stories -jest.mock('../public/lib/es_service', () => ({ - getDefaultIndex: () => Promise.resolve('test index'), -})); - addSerializer(styleSheetSerializer); const emotionSerializer = createSerializer({ diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants.ts index 89c7de48b257d..30c1d7d186de0 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -141,7 +141,7 @@ export const MAX_CONCURRENT_SEARCHES = 10 as const; * Validation */ -export const MAX_TITLE_LENGTH = 64 as const; +export const MAX_TITLE_LENGTH = 160 as const; /** * Cases features diff --git a/x-pack/plugins/cases/docs/openapi/bundled-min.json b/x-pack/plugins/cases/docs/openapi/bundled-min.json index 02e04d2b2fbdd..a3f9567b32b3d 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled-min.json +++ b/x-pack/plugins/cases/docs/openapi/bundled-min.json @@ -816,6 +816,24 @@ "title" ], "properties": { + "assignees": { + "type": "array", + "description": "An array containing users that are assigned to the case.", + "nullable": true, + "items": { + "type": "object", + "required": [ + "uid" + ], + "properties": { + "uid": { + "type": "string", + "description": "A unique identifier for the user profile. These identifiers can be found by using the suggest user profile API.", + "example": "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + } + } + } + }, "connector": { "oneOf": [ { @@ -1272,6 +1290,24 @@ "version" ], "properties": { + "assignees": { + "type": "array", + "description": "An array containing users that are assigned to the case.", + "nullable": true, + "items": { + "type": "object", + "required": [ + "uid" + ], + "properties": { + "uid": { + "type": "string", + "description": "A unique identifier for the user profile. You can use the get user profile API to retrieve more details.", + "example": "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + } + } + } + }, "closed_at": { "type": "string", "format": "date-time", @@ -1417,6 +1453,24 @@ "version" ], "properties": { + "assignees": { + "type": "array", + "description": "An array containing users that are assigned to the case.", + "nullable": true, + "items": { + "type": "object", + "required": [ + "uid" + ], + "properties": { + "uid": { + "type": "string", + "description": "A unique identifier for the user profile. These identifiers can be found by using the suggest user profile API.", + "example": "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0" + } + } + } + }, "connector": { "oneOf": [ { @@ -1746,6 +1800,7 @@ "tags": [ "tag 1" ], + "assignees": [], "description": "A case description.", "settings": { "syncAlerts": false @@ -1840,6 +1895,7 @@ "full_name": null, "username": "elastic" }, + "assignees": [], "connector": { "id": "131d4448-abe0-4789-939d-8ef60680b498", "name": "My connector", diff --git a/x-pack/plugins/cases/docs/openapi/bundled-min.yaml b/x-pack/plugins/cases/docs/openapi/bundled-min.yaml index d90783ccb7309..43962944ee9f7 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled-min.yaml +++ b/x-pack/plugins/cases/docs/openapi/bundled-min.yaml @@ -560,6 +560,19 @@ components: - tags - title properties: + assignees: + type: array + description: An array containing users that are assigned to the case. + nullable: true + items: + type: object + required: + - uid + properties: + uid: + type: string + description: A unique identifier for the user profile. These identifiers can be found by using the suggest user profile API. + example: u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0 connector: oneOf: - $ref: '#/components/schemas/connector_properties_none' @@ -896,6 +909,19 @@ components: - updated_by - version properties: + assignees: + type: array + description: An array containing users that are assigned to the case. + nullable: true + items: + type: object + required: + - uid + properties: + uid: + type: string + description: A unique identifier for the user profile. You can use the get user profile API to retrieve more details. + example: u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0 closed_at: type: string format: date-time @@ -992,6 +1018,19 @@ components: - id - version properties: + assignees: + type: array + description: An array containing users that are assigned to the case. + nullable: true + items: + type: object + required: + - uid + properties: + uid: + type: string + description: A unique identifier for the user profile. These identifiers can be found by using the suggest user profile API. + example: u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0 connector: oneOf: - $ref: '#/components/schemas/connector_properties_none' @@ -1222,6 +1261,7 @@ components: title: Case title 1 tags: - tag 1 + assignees: [] description: A case description. settings: syncAlerts: false @@ -1296,6 +1336,7 @@ components: email: null full_name: null username: elastic + assignees: [] connector: id: 131d4448-abe0-4789-939d-8ef60680b498 name: My connector diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/create_case_response.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/create_case_response.yaml index 7d9cdf3626c72..8cd80595abf30 100644 --- a/x-pack/plugins/cases/docs/openapi/components/examples/create_case_response.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/examples/create_case_response.yaml @@ -8,6 +8,7 @@ value: title: Case title 1 tags: - tag 1 + assignees: [] description: A case description. settings: syncAlerts: false diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/find_case_response.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/find_case_response.yaml index 1c8168dde7708..6f744d03a1365 100644 --- a/x-pack/plugins/cases/docs/openapi/components/examples/find_case_response.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/examples/find_case_response.yaml @@ -33,6 +33,7 @@ value: "full_name": null, "username": "elastic" }, + "assignees": [], "connector": { "id": "none", "name": "none", diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/get_case_response.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/get_case_response.yaml index 936a21a5cfc70..bd74fa423bb9c 100644 --- a/x-pack/plugins/cases/docs/openapi/components/examples/get_case_response.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/examples/get_case_response.yaml @@ -34,7 +34,9 @@ value: "created_at":"2022-07-13T15:33:50.604Z", "created_by":{"username":"elastic","email":null,"full_name":null},"status":"open", "updated_at":"2022-07-13T15:40:32.335Z", - "updated_by":{"full_name":null,"email":null,"username":"elastic"},"connector":{ + "updated_by":{"full_name":null,"email":null,"username":"elastic"}, + "assignees":[{"uid":"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"}], + "connector":{ "id":"none", "name":"none", "type":".none", diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/update_case_response.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/update_case_response.yaml index 7413547e6ff60..eaf421771b51e 100644 --- a/x-pack/plugins/cases/docs/openapi/components/examples/update_case_response.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/examples/update_case_response.yaml @@ -31,6 +31,7 @@ value: "full_name": null, "username": "elastic" }, + "assignees": [], "connector": { "id": "131d4448-abe0-4789-939d-8ef60680b498", "name": "My connector", diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/action_types.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/action_types.yaml index 05e3fc6ab04b7..3568008b07000 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/action_types.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/action_types.yaml @@ -1,6 +1,7 @@ type: string description: The type of action. enum: + - assignees - create_case - comment - connector diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml index a53d88f3be69b..1caa1643476d5 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/case_response_properties.yaml @@ -22,7 +22,20 @@ required: - updated_at - updated_by - version -properties: +properties: + assignees: + type: array + description: An array containing users that are assigned to the case. + nullable: true + items: + type: object + required: + - uid + properties: + uid: + type: string + description: A unique identifier for the user profile. You can use the get user profile API to retrieve more details. + example: u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0 closed_at: type: string format: date-time diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/create_case_request.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/create_case_request.yaml index ab6b49c653668..715bfaf112042 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/create_case_request.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/create_case_request.yaml @@ -10,6 +10,19 @@ required: - tags - title properties: + assignees: + type: array + description: An array containing users that are assigned to the case. + nullable: true + items: + type: object + required: + - uid + properties: + uid: + type: string + description: A unique identifier for the user profile. These identifiers can be found by using the suggest user profile API. + example: u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0 connector: oneOf: - $ref: 'connector_properties_none.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/payload_assignees.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/payload_assignees.yaml new file mode 100644 index 0000000000000..122541dfe4fe6 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/payload_assignees.yaml @@ -0,0 +1,9 @@ +type: object +properties: + assignees: + type: array + items: + type: object + properties: + uid: + type: string \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/update_case_request.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/update_case_request.yaml index f6feac43b1613..ee4249aeaf9d3 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/update_case_request.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/update_case_request.yaml @@ -13,7 +13,20 @@ properties: required: - id - version - properties: + properties: + assignees: + type: array + description: An array containing users that are assigned to the case. + nullable: true + items: + type: object + required: + - uid + properties: + uid: + type: string + description: A unique identifier for the user profile. These identifiers can be found by using the suggest user profile API. + example: u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0 connector: oneOf: - $ref: 'connector_properties_none.yaml' diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/user_actions_response_properties.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/user_actions_response_properties.yaml index e828f3441cb5d..a3f57ed53297d 100644 --- a/x-pack/plugins/cases/docs/openapi/components/schemas/user_actions_response_properties.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/schemas/user_actions_response_properties.yaml @@ -24,6 +24,7 @@ properties: payload: oneOf: - $ref: 'payload_alert_comment.yaml' + - $ref: 'payload_assignees.yaml' - $ref: 'payload_connector.yaml' - $ref: 'payload_create_case.yaml' - $ref: 'payload_description.yaml' diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx index 217893b0923e0..89bd623307f1f 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/all_cases_selector_modal.tsx @@ -29,8 +29,8 @@ export interface AllCasesSelectorModalProps { const Modal = styled(EuiModal)` ${({ theme }) => ` - width: ${theme.eui.euiBreakpoints.l}; - max-width: ${theme.eui.euiBreakpoints.l}; + width: ${theme.eui.euiBreakpoints.xl}; + max-width: ${theme.eui.euiBreakpoints.xl}; `} `; diff --git a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx index 424ad669c5b34..8185b3be3df3e 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.test.tsx @@ -52,6 +52,7 @@ describe('useCasesColumns ', () => { Object { "name": "Name", "render": [Function], + "width": "20%", }, Object { "field": "assignees", @@ -130,6 +131,7 @@ describe('useCasesColumns ', () => { Object { "name": "Name", "render": [Function], + "width": "20%", }, Object { "field": "assignees", @@ -199,6 +201,7 @@ describe('useCasesColumns ', () => { Object { "name": "Name", "render": [Function], + "width": "20%", }, Object { "field": "assignees", @@ -262,6 +265,7 @@ describe('useCasesColumns ', () => { Object { "name": "Name", "render": [Function], + "width": "20%", }, Object { "field": "tags", @@ -331,6 +335,7 @@ describe('useCasesColumns ', () => { Object { "name": "Name", "render": [Function], + "width": "20%", }, Object { "field": "tags", @@ -398,6 +403,7 @@ describe('useCasesColumns ', () => { Object { "name": "Name", "render": [Function], + "width": "20%", }, Object { "field": "tags", @@ -464,6 +470,7 @@ describe('useCasesColumns ', () => { Object { "name": "Name", "render": [Function], + "width": "20%", }, Object { "field": "tags", @@ -529,6 +536,7 @@ describe('useCasesColumns ', () => { Object { "name": "Name", "render": [Function], + "width": "20%", }, Object { "field": "tags", diff --git a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx index 965277abd2dd3..3f0009d42f591 100644 --- a/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/use_cases_columns.tsx @@ -160,6 +160,7 @@ export const useCasesColumns = ({ } return getEmptyTagValue(); }, + width: '20%', }, ]; diff --git a/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx b/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx index 51d42c3d1d3b0..f0ecca120ac10 100644 --- a/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx +++ b/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx @@ -190,9 +190,8 @@ describe('EditableTitle', () => { expect(wrapper.find('[data-test-subj="editable-title-edit-icon"]').first().exists()).toBe(true); }); - it('does not submit the title when the length is longer than 64 characters', () => { - const longTitle = - 'This is a title that should not be saved as it is longer than 64 characters.'; + it('does not submit the title when the length is longer than 160 characters', () => { + const longTitle = 'a'.repeat(161); const wrapper = mount( @@ -210,7 +209,7 @@ describe('EditableTitle', () => { wrapper.find('button[data-test-subj="editable-title-submit-btn"]').simulate('click'); wrapper.update(); expect(wrapper.find('.euiFormErrorText').text()).toBe( - 'The length of the title is too long. The maximum length is 64.' + 'The length of the title is too long. The maximum length is 160.' ); expect(submitTitle).not.toHaveBeenCalled(); @@ -220,8 +219,7 @@ describe('EditableTitle', () => { }); it('does not show an error after a previous edit error was displayed', () => { - const longTitle = - 'This is a title that should not be saved as it is longer than 64 characters.'; + const longTitle = 'a'.repeat(161); const shortTitle = 'My title'; const wrapper = mount( @@ -241,7 +239,7 @@ describe('EditableTitle', () => { wrapper.find('button[data-test-subj="editable-title-submit-btn"]').simulate('click'); wrapper.update(); expect(wrapper.find('.euiFormErrorText').text()).toBe( - 'The length of the title is too long. The maximum length is 64.' + 'The length of the title is too long. The maximum length is 160.' ); // write a shorter one diff --git a/x-pack/plugins/cases/public/components/links/index.test.tsx b/x-pack/plugins/cases/public/components/links/index.test.tsx index e2da1a28261e7..2fd05e45ade25 100644 --- a/x-pack/plugins/cases/public/components/links/index.test.tsx +++ b/x-pack/plugins/cases/public/components/links/index.test.tsx @@ -74,7 +74,7 @@ describe('Configuration button', () => { test('it shows the tooltip when hovering the button', () => { // Use fake timers so we don't have to wait for the EuiToolTip timeout - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const msgTooltip = 'My message tooltip'; const titleTooltip = 'My title'; diff --git a/x-pack/plugins/cases/public/components/property_actions/index.tsx b/x-pack/plugins/cases/public/components/property_actions/index.tsx index 9fa874344864b..8f67611097a80 100644 --- a/x-pack/plugins/cases/public/components/property_actions/index.tsx +++ b/x-pack/plugins/cases/public/components/property_actions/index.tsx @@ -45,8 +45,8 @@ export const PropertyActions = React.memo(({ propertyActio const [showActions, setShowActions] = useState(false); const onButtonClick = useCallback(() => { - setShowActions(!showActions); - }, [showActions]); + setShowActions((prevShowActions) => !prevShowActions); + }, []); const onClosePopover = useCallback((cb?: () => void) => { setShowActions(false); diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/actions.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/actions.tsx index b82e10b8065a4..5b17b05a45f68 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/actions.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/actions.tsx @@ -5,11 +5,9 @@ * 2.0. */ -import React, { useContext } from 'react'; +import React from 'react'; import classNames from 'classnames'; -import { ThemeContext } from 'styled-components'; -import { EuiToken } from '@elastic/eui'; import type { CommentResponseActionsType } from '../../../../common/api'; import type { UserActionBuilder, UserActionBuilderArgs } from '../types'; import { UserActionTimestamp } from '../timestamp'; @@ -54,7 +52,7 @@ export const createActionAttachmentUserActionBuilder = ({ ), 'data-test-subj': 'endpoint-action', timestamp: , - timelineAvatar: , + timelineAvatar: comment.actions.type === 'isolate' ? 'lock' : 'lockOpen', actions: , children: comment.comment.trim().length > 0 && ( @@ -65,21 +63,3 @@ export const createActionAttachmentUserActionBuilder = ({ ]; }, }); - -const ActionIcon = React.memo<{ - actionType: string; -}>(({ actionType }) => { - const theme = useContext(ThemeContext); - return ( - - ); -}); - -ActionIcon.displayName = 'ActionIcon'; diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/alert.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/alert.tsx index 20afa12a377bf..049862abaaf40 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/alert.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/alert.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { get, isEmpty } from 'lodash'; import type { EuiCommentProps } from '@elastic/eui'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiFlexItem } from '@elastic/eui'; import { ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; import type { CommentResponseAlertsType } from '../../../../common/api'; @@ -16,10 +16,11 @@ import type { UserActionBuilder, UserActionBuilderArgs } from '../types'; import { UserActionTimestamp } from '../timestamp'; import type { SnakeToCamelCase } from '../../../../common/types'; import { MultipleAlertsCommentEvent, SingleAlertCommentEvent } from './alert_event'; -import { UserActionCopyLink } from '../copy_link'; import { UserActionShowAlert } from './show_alert'; import { ShowAlertTableLink } from './show_alert_table_link'; import { HoverableUserWithAvatarResolver } from '../../user_profiles/hoverable_user_with_avatar_resolver'; +import { UserActionContentToolbar } from '../content_toolbar'; +import { AlertPropertyActions } from '../property_actions/alert_property_actions'; type BuilderArgs = Pick< UserActionBuilderArgs, @@ -30,6 +31,8 @@ type BuilderArgs = Pick< | 'loadingAlertData' | 'onShowAlertDetails' | 'userProfiles' + | 'handleDeleteComment' + | 'loadingCommentIds' > & { comment: SnakeToCamelCase }; const getSingleAlertUserAction = ({ @@ -37,10 +40,12 @@ const getSingleAlertUserAction = ({ userProfiles, comment, alertData, - getRuleDetailsHref, loadingAlertData, + loadingCommentIds, + getRuleDetailsHref, onRuleDetailsClick, onShowAlertDetails, + handleDeleteComment, }: BuilderArgs): EuiCommentProps[] => { const alertId = getNonEmptyField(comment.alertId); const alertIndex = getNonEmptyField(comment.index); @@ -73,10 +78,7 @@ const getSingleAlertUserAction = ({ timestamp: , timelineAvatar: 'bell', actions: ( - - - - + - + handleDeleteComment(comment.id)} + isLoading={loadingCommentIds.includes(comment.id)} + totalAlerts={1} + /> + ), }, ]; @@ -96,9 +103,11 @@ const getMultipleAlertsUserAction = ({ userProfiles, comment, alertData, - getRuleDetailsHref, loadingAlertData, + loadingCommentIds, + getRuleDetailsHref, onRuleDetailsClick, + handleDeleteComment, }: BuilderArgs): EuiCommentProps[] => { if (!Array.isArray(comment.alertId)) { return []; @@ -128,14 +137,16 @@ const getMultipleAlertsUserAction = ({ timestamp: , timelineAvatar: 'bell', actions: ( - - - - + - + handleDeleteComment(comment.id)} + isLoading={loadingCommentIds.includes(comment.id)} + totalAlerts={totalAlerts} + /> + ), }, ]; diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/comment.test.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/comment.test.tsx index 46562a028e536..3415355354ed9 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/comment.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/comment.test.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { EuiCommentList } from '@elastic/eui'; +import type { RenderResult } from '@testing-library/react'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; @@ -20,6 +21,7 @@ import { getExternalReferenceAttachment, getExternalReferenceUserAction, getHostIsolationUserAction, + getMultipleAlertsUserAction, getPersistableStateAttachment, getPersistableStateUserAction, getUserAction, @@ -30,7 +32,7 @@ import type { AppMockRenderer } from '../../../common/mock'; import { createAppMockRenderer, TestProviders } from '../../../common/mock'; import { createCommentUserActionBuilder } from './comment'; import { getMockBuilderArgs } from '../mock'; -import { useCaseViewParams } from '../../../common/navigation'; +import { useCaseViewNavigation, useCaseViewParams } from '../../../common/navigation'; import { ExternalReferenceAttachmentTypeRegistry } from '../../../client/attachment_framework/external_reference_registry'; import { PersistableStateAttachmentTypeRegistry } from '../../../client/attachment_framework/persistable_state_registry'; import { userProfiles } from '../../../containers/user_profiles/api.mock'; @@ -39,66 +41,231 @@ jest.mock('../../../common/lib/kibana'); jest.mock('../../../common/navigation/hooks'); const useCaseViewParamsMock = useCaseViewParams as jest.Mock; +const useCaseViewNavigationMock = useCaseViewNavigation as jest.Mock; +const navigateToCaseView = jest.fn(); describe('createCommentUserActionBuilder', () => { const builderArgs = getMockBuilderArgs(); beforeEach(() => { jest.clearAllMocks(); + useCaseViewNavigationMock.mockReturnValue({ navigateToCaseView }); }); - it('renders correctly when editing a comment', async () => { - const userAction = getUserAction('comment', Actions.update); - const builder = createCommentUserActionBuilder({ - ...builderArgs, - userAction, - }); + describe('edits', () => { + it('renders correctly when editing a comment', async () => { + const userAction = getUserAction('comment', Actions.update); + const builder = createCommentUserActionBuilder({ + ...builderArgs, + userAction, + }); - const createdUserAction = builder.build(); - render( - - - - ); + const createdUserAction = builder.build(); + render( + + + + ); - expect(screen.getByText('edited comment')).toBeInTheDocument(); + expect(screen.getByText('edited comment')).toBeInTheDocument(); + }); }); - it('renders correctly when deleting a comment', async () => { - const userAction = getUserAction('comment', Actions.delete); - const builder = createCommentUserActionBuilder({ - ...builderArgs, - userAction, + describe('deletions', () => { + it('renders correctly when deleting a comment', async () => { + const userAction = getUserAction('comment', Actions.delete); + const builder = createCommentUserActionBuilder({ + ...builderArgs, + userAction, + }); + + const createdUserAction = builder.build(); + render( + + + + ); + + expect(screen.getByText('removed comment')).toBeInTheDocument(); }); - const createdUserAction = builder.build(); - render( - - - - ); + it('renders correctly when deleting a single alert', async () => { + const userAction = getAlertUserAction({ action: Actions.delete }); + const builder = createCommentUserActionBuilder({ + ...builderArgs, + userAction, + }); + + const createdUserAction = builder.build(); + render( + + + + ); + + expect(screen.getByText('removed one alert')).toBeInTheDocument(); + }); + + it('renders correctly when deleting multiple alerts', async () => { + const userAction = getMultipleAlertsUserAction({ action: Actions.delete }); + const builder = createCommentUserActionBuilder({ + ...builderArgs, + userAction, + }); - expect(screen.getByText('removed comment')).toBeInTheDocument(); + const createdUserAction = builder.build(); + render( + + + + ); + + expect(screen.getByText('removed 2 alerts')).toBeInTheDocument(); + }); + + it('renders correctly when deleting an external reference attachment', async () => { + const userAction = getExternalReferenceUserAction({ action: Actions.delete }); + const builder = createCommentUserActionBuilder({ + ...builderArgs, + userAction, + }); + + const createdUserAction = builder.build(); + render( + + + + ); + + expect(screen.getByText('removed attachment')).toBeInTheDocument(); + }); + + it('renders correctly when deleting a persistable state attachment', async () => { + const userAction = getPersistableStateUserAction({ action: Actions.delete }); + const builder = createCommentUserActionBuilder({ + ...builderArgs, + userAction, + }); + + const createdUserAction = builder.build(); + render( + + + + ); + + expect(screen.getByText('removed attachment')).toBeInTheDocument(); + }); }); - it('renders correctly a user comment', async () => { - const userAction = getUserAction('comment', Actions.create, { - commentId: basicCase.comments[0].id, + describe('user comments', () => { + it('renders correctly a user comment', async () => { + const userAction = getUserAction('comment', Actions.create, { + commentId: basicCase.comments[0].id, + }); + + const builder = createCommentUserActionBuilder({ + ...builderArgs, + userAction, + }); + + const createdUserAction = builder.build(); + render( + + + + ); + + expect(screen.getByText('Solve this fast!')).toBeInTheDocument(); }); - const builder = createCommentUserActionBuilder({ - ...builderArgs, - userAction, + it('deletes a user comment correctly', async () => { + const userAction = getUserAction('comment', Actions.create, { + commentId: basicCase.comments[0].id, + }); + + const builder = createCommentUserActionBuilder({ + ...builderArgs, + userAction, + }); + + const createdUserAction = builder.build(); + const result = render( + + + + ); + + expect(result.getByText('Solve this fast!')).toBeInTheDocument(); + + await deleteAttachment(result, 'trash', 'Delete'); + + await waitFor(() => { + expect(builderArgs.handleDeleteComment).toHaveBeenCalledWith('basic-comment-id'); + }); }); - const createdUserAction = builder.build(); - render( - - - - ); + it('edits a user comment correctly', async () => { + const userAction = getUserAction('comment', Actions.create, { + commentId: basicCase.comments[0].id, + }); - expect(screen.getByText('Solve this fast!')).toBeInTheDocument(); + const builder = createCommentUserActionBuilder({ + ...builderArgs, + userAction, + }); + + const createdUserAction = builder.build(); + const result = render( + + + + ); + + expect(result.getByText('Solve this fast!')).toBeInTheDocument(); + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-pencil')).toBeInTheDocument(); + userEvent.click(result.getByTestId('property-actions-pencil')); + + await waitFor(() => { + expect(builderArgs.handleManageMarkdownEditId).toHaveBeenCalledWith('basic-comment-id'); + }); + }); + + it('quotes a user comment correctly', async () => { + const userAction = getUserAction('comment', Actions.create, { + commentId: basicCase.comments[0].id, + }); + + const builder = createCommentUserActionBuilder({ + ...builderArgs, + userAction, + }); + + const createdUserAction = builder.build(); + const result = render( + + + + ); + + expect(result.getByText('Solve this fast!')).toBeInTheDocument(); + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-quote')).toBeInTheDocument(); + userEvent.click(result.getByTestId('property-actions-quote')); + + await waitFor(() => { + expect(builderArgs.handleManageQuote).toHaveBeenCalledWith('basic-comment-id'); + }); + }); }); describe('Single alert', () => { @@ -125,9 +292,72 @@ describe('createCommentUserActionBuilder', () => { 'added an alert from Awesome rule' ); }); + + it('deletes a single alert correctly', async () => { + const userAction = getAlertUserAction(); + + const builder = createCommentUserActionBuilder({ + ...builderArgs, + caseData: { + ...builderArgs.caseData, + comments: [alertComment], + }, + userAction, + }); + + const createdUserAction = builder.build(); + const res = render( + + + + ); + + expect(res.getByTestId('single-alert-user-action-alert-action-id')).toHaveTextContent( + 'added an alert from Awesome rule' + ); + + await deleteAttachment(res, 'minusInCircle', 'Remove'); + + await waitFor(() => { + expect(builderArgs.handleDeleteComment).toHaveBeenCalledWith('alert-comment-id'); + }); + }); + + it('views an alert correctly', async () => { + const userAction = getAlertUserAction(); + + const builder = createCommentUserActionBuilder({ + ...builderArgs, + caseData: { + ...builderArgs.caseData, + comments: [alertComment], + }, + userAction, + }); + + const createdUserAction = builder.build(); + const result = render( + + + + ); + + expect(result.getByTestId('comment-action-show-alert-alert-action-id')).toBeInTheDocument(); + userEvent.click(result.getByTestId('comment-action-show-alert-alert-action-id')); + + await waitFor(() => { + expect(builderArgs.onShowAlertDetails).toHaveBeenCalledWith('alert-id-1', 'alert-index-1'); + }); + }); }); describe('Multiple alerts', () => { + let appMockRender: AppMockRenderer; + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + }); + it('renders correctly multiple alerts with a link to the alerts table', async () => { useCaseViewParamsMock.mockReturnValue({ detailName: '1234' }); const userAction = getAlertUserAction(); @@ -148,16 +378,74 @@ describe('createCommentUserActionBuilder', () => { }); const createdUserAction = builder.build(); - render( - - - + const res = appMockRender.render(); + + expect(res.getByTestId('multiple-alerts-user-action-alert-action-id')).toHaveTextContent( + 'added 2 alerts from Awesome rule' ); + expect(res.getByTestId('comment-action-show-alerts-1234')); + }); + + it('deletes multiple alerts correctly', async () => { + const userAction = getAlertUserAction(); + + const builder = createCommentUserActionBuilder({ + ...builderArgs, + caseData: { + ...builderArgs.caseData, + comments: [ + { + ...alertComment, + alertId: ['alert-id-1', 'alert-id-2'], + index: ['alert-index-1', 'alert-index-2'], + }, + ], + }, + userAction, + }); + + const createdUserAction = builder.build(); + const res = appMockRender.render(); expect(screen.getByTestId('multiple-alerts-user-action-alert-action-id')).toHaveTextContent( 'added 2 alerts from Awesome rule' ); - expect(screen.getByTestId('comment-action-show-alerts-1234')); + + await deleteAttachment(res, 'minusInCircle', 'Remove'); + + await waitFor(() => { + expect(builderArgs.handleDeleteComment).toHaveBeenCalledWith('alert-comment-id'); + }); + }); + + it('views multiple alerts correctly', async () => { + useCaseViewParamsMock.mockReturnValue({ detailName: '1234' }); + const userAction = getAlertUserAction(); + + const builder = createCommentUserActionBuilder({ + ...builderArgs, + caseData: { + ...builderArgs.caseData, + comments: [ + { + ...alertComment, + alertId: ['alert-id-1', 'alert-id-2'], + index: ['alert-index-1', 'alert-index-2'], + }, + ], + }, + userAction, + }); + + const createdUserAction = builder.build(); + const res = appMockRender.render(); + + expect(res.getByTestId('comment-action-show-alerts-1234')); + userEvent.click(res.getByTestId('comment-action-show-alerts-1234')); + + await waitFor(() => { + expect(navigateToCaseView).toHaveBeenCalledWith({ detailName: '1234', tabId: 'alerts' }); + }); }); }); @@ -297,20 +585,8 @@ describe('createCommentUserActionBuilder', () => { const result = appMockRender.render(); expect(result.getByTestId('comment-externalReference-.test')).toBeInTheDocument(); - expect(result.getByTestId('property-actions')).toBeInTheDocument(); - userEvent.click(result.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - - expect(result.queryByTestId('property-actions-trash')).toBeInTheDocument(); - - userEvent.click(result.getByTestId('property-actions-trash')); - - await waitFor(() => { - expect(result.queryByTestId('property-actions-confirm-modal')).toBeInTheDocument(); - }); - - userEvent.click(result.getByText('Delete')); + await deleteAttachment(result, 'trash', 'Delete'); await waitFor(() => { expect(builderArgs.handleDeleteComment).toHaveBeenCalledWith( @@ -480,20 +756,8 @@ describe('createCommentUserActionBuilder', () => { const result = appMockRender.render(); expect(result.getByTestId('comment-persistableState-.test')).toBeInTheDocument(); - expect(result.getByTestId('property-actions')).toBeInTheDocument(); - - userEvent.click(result.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - - expect(result.queryByTestId('property-actions-trash')).toBeInTheDocument(); - userEvent.click(result.getByTestId('property-actions-trash')); - - await waitFor(() => { - expect(result.queryByTestId('property-actions-confirm-modal')).toBeInTheDocument(); - }); - - userEvent.click(result.getByText('Delete')); + await deleteAttachment(result, 'trash', 'Delete'); await waitFor(() => { expect(builderArgs.handleDeleteComment).toHaveBeenCalledWith( @@ -503,3 +767,20 @@ describe('createCommentUserActionBuilder', () => { }); }); }); + +const deleteAttachment = async (result: RenderResult, deleteIcon: string, buttonLabel: string) => { + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId(`property-actions-${deleteIcon}`)).toBeInTheDocument(); + + userEvent.click(result.getByTestId(`property-actions-${deleteIcon}`)); + + await waitFor(() => { + expect(result.queryByTestId('property-actions-confirm-modal')).toBeInTheDocument(); + }); + + userEvent.click(result.getByText(buttonLabel)); +}; diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/comment.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/comment.tsx index 4e2ecb85e22ba..0de6fcb91a5e8 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/comment.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/comment.tsx @@ -12,7 +12,7 @@ import { Actions, CommentType } from '../../../../common/api'; import type { UserActionBuilder, UserActionBuilderArgs, UserActionResponse } from '../types'; import { createCommonUpdateUserActionBuilder } from '../common'; import type { Comment } from '../../../containers/types'; -import * as i18n from '../translations'; +import * as i18n from './translations'; import { createUserAttachmentUserActionBuilder } from './user'; import { createAlertAttachmentUserActionBuilder } from './alert'; import { createActionAttachmentUserActionBuilder } from './actions'; @@ -20,7 +20,25 @@ import { createExternalReferenceAttachmentUserActionBuilder } from './external_r import { createPersistableStateAttachmentUserActionBuilder } from './persistable_state'; const getUpdateLabelTitle = () => `${i18n.EDITED_FIELD} ${i18n.COMMENT.toLowerCase()}`; -const getDeleteLabelTitle = () => `${i18n.REMOVED_FIELD} ${i18n.COMMENT.toLowerCase()}`; +const getDeleteLabelTitle = (userAction: UserActionResponse) => { + const { comment } = userAction.payload; + + if (comment.type === CommentType.alert) { + const totalAlerts = Array.isArray(comment.alertId) ? comment.alertId.length : 1; + const alertLabel = i18n.MULTIPLE_ALERTS(totalAlerts); + + return `${i18n.REMOVED_FIELD} ${alertLabel}`; + } + + if ( + comment.type === CommentType.externalReference || + comment.type === CommentType.persistableState + ) { + return `${i18n.REMOVED_FIELD} ${i18n.ATTACHMENT.toLowerCase()}`; + } + + return `${i18n.REMOVED_FIELD} ${i18n.COMMENT.toLowerCase()}`; +}; const getDeleteCommentUserAction = ({ userAction, @@ -29,7 +47,7 @@ const getDeleteCommentUserAction = ({ }: { userAction: UserActionResponse; } & Pick): EuiCommentProps[] => { - const label = getDeleteLabelTitle(); + const label = getDeleteLabelTitle(userAction); const commonBuilder = createCommonUpdateUserActionBuilder({ userAction, userProfiles, @@ -96,6 +114,8 @@ const getCreateCommentUserAction = ({ loadingAlertData, onRuleDetailsClick, onShowAlertDetails, + handleDeleteComment, + loadingCommentIds, }); return alertBuilder.build(); diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx index fc2c24f225898..da20f4c4cefc3 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/registered_attachments.tsx @@ -23,8 +23,8 @@ import { UserActionTimestamp } from '../timestamp'; import type { SnakeToCamelCase } from '../../../../common/types'; import { ATTACHMENT_NOT_REGISTERED_ERROR, DEFAULT_EVENT_ATTACHMENT_TITLE } from './translations'; import { UserActionContentToolbar } from '../content_toolbar'; -import * as i18n from '../translations'; import { HoverableUserWithAvatarResolver } from '../../user_profiles/hoverable_user_with_avatar_resolver'; +import { RegisteredAttachmentsPropertyActions } from '../property_actions/registered_attachments_property_actions'; type BuilderArgs = Pick< UserActionBuilderArgs, @@ -123,17 +123,13 @@ export const createRegisteredAttachmentUserActionBuilder = < timestamp: , timelineAvatar: attachmentViewObject.timelineAvatar, actions: ( - <> - + {attachmentViewObject.actions} + handleDeleteComment(comment.id)} - extraActions={attachmentViewObject.actions} /> - + ), children: renderer(props), }, diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/show_alert.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/show_alert.tsx index 5506cbd5d7d00..48a6bff3fd557 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/show_alert.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/show_alert.tsx @@ -26,6 +26,7 @@ const UserActionShowAlertComponent = ({ () => onShowAlertDetails(alertId, index), [alertId, index, onShowAlertDetails] ); + return ( {i18n.SHOW_ALERT_TOOLTIP}

    }> + i18n.translate('xpack.cases.caseView.alerts.multipleAlerts', { + values: { totalAlerts }, + defaultMessage: + '{totalAlerts, plural, =1 {one} other {{totalAlerts}}} {totalAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const ATTACHMENT = i18n.translate('xpack.cases.userActions.attachment', { + defaultMessage: 'Attachment', +}); diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/user.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/user.tsx index 91df42cee7e4e..5331b215d1880 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/user.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/user.tsx @@ -13,10 +13,10 @@ import { UserActionTimestamp } from '../timestamp'; import type { SnakeToCamelCase } from '../../../../common/types'; import { UserActionMarkdown } from '../markdown_form'; import { UserActionContentToolbar } from '../content_toolbar'; -import * as i18n from '../translations'; import type { UserActionBuilderArgs, UserActionBuilder } from '../types'; import { HoverableUsernameResolver } from '../../user_profiles/hoverable_username_resolver'; import { HoverableAvatarResolver } from '../../user_profiles/hoverable_avatar_resolver'; +import { UserCommentPropertyActions } from '../property_actions/user_comment_property_actions'; type BuilderArgs = Pick< UserActionBuilderArgs, @@ -76,18 +76,15 @@ export const createUserAttachmentUserActionBuilder = ({ ), actions: ( - + + handleManageMarkdownEditId(comment.id)} + onDelete={() => handleDeleteComment(comment.id)} + onQuote={() => handleManageQuote(comment.id)} + /> + ), }, ], diff --git a/x-pack/plugins/cases/public/components/user_actions/content_toolbar.test.tsx b/x-pack/plugins/cases/public/components/user_actions/content_toolbar.test.tsx index bc8d79b25e4e6..79e3e256d3deb 100644 --- a/x-pack/plugins/cases/public/components/user_actions/content_toolbar.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/content_toolbar.test.tsx @@ -6,38 +6,26 @@ */ import React from 'react'; -import type { ReactWrapper } from 'enzyme'; -import { mount } from 'enzyme'; -import type { UserActionContentToolbarProps } from './content_toolbar'; import { UserActionContentToolbar } from './content_toolbar'; -import { TestProviders } from '../../common/mock'; +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; jest.mock('../../common/navigation/hooks'); jest.mock('../../common/lib/kibana'); -const props: UserActionContentToolbarProps = { - commentMarkdown: '', - id: '1', - editLabel: 'edit', - quoteLabel: 'quote', - isLoading: false, - onEdit: jest.fn(), - onQuote: jest.fn(), -}; - describe('UserActionContentToolbar ', () => { - let wrapper: ReactWrapper; + let appMockRenderer: AppMockRenderer; - beforeAll(() => { - wrapper = mount( - - - - ); + beforeEach(() => { + appMockRenderer = createAppMockRenderer(); }); - it('it renders', async () => { - expect(wrapper.find(`[data-test-subj="copy-link-${props.id}"]`).first().exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="property-actions"]').first().exists()).toBeTruthy(); + it('renders', async () => { + const res = appMockRenderer.render( + {'My children'} + ); + + res.getByTestId('copy-link-1'); + res.getByText('My children'); }); }); diff --git a/x-pack/plugins/cases/public/components/user_actions/content_toolbar.tsx b/x-pack/plugins/cases/public/components/user_actions/content_toolbar.tsx index a37f2f894608b..e7d1dd7ba5eaa 100644 --- a/x-pack/plugins/cases/public/components/user_actions/content_toolbar.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/content_toolbar.tsx @@ -6,64 +6,31 @@ */ import React, { memo } from 'react'; -import type { EuiCommentProps } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { UserActionCopyLink } from './copy_link'; -import type { Actions } from './property_actions'; -import { UserActionPropertyActions } from './property_actions'; export interface UserActionContentToolbarProps { - commentMarkdown?: string; id: string; - actions?: Actions; - editLabel?: string; - deleteLabel?: string; - deleteConfirmTitle?: string; - quoteLabel?: string; - isLoading: boolean; - extraActions?: EuiCommentProps['actions']; - onEdit?: (id: string) => void; - onQuote?: (id: string) => void; - onDelete?: (id: string) => void; + children: React.ReactNode; + withCopyLinkAction?: boolean; } -const UserActionContentToolbarComponent = ({ - commentMarkdown, +const UserActionContentToolbarComponent: React.FC = ({ id, - actions, - editLabel, - deleteLabel, - deleteConfirmTitle, - quoteLabel, - isLoading, - extraActions, - onEdit, - onQuote, - onDelete, -}: UserActionContentToolbarProps) => ( + withCopyLinkAction = true, + children, +}) => ( - - - - - - - {extraActions != null ? {extraActions} : null} + {withCopyLinkAction ? ( + + + + ) : null} + {children} ); + UserActionContentToolbarComponent.displayName = 'UserActionContentToolbar'; export const UserActionContentToolbar = memo(UserActionContentToolbarComponent); diff --git a/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx b/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx new file mode 100644 index 0000000000000..b4e53e21a0df9 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.test.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; +import { DeleteAttachmentConfirmationModal } from './delete_attachment_confirmation_modal'; + +describe('DeleteAttachmentConfirmationModal', () => { + let appMock: AppMockRenderer; + const props = { + title: 'My title', + confirmButtonText: 'My button text', + onCancel: jest.fn(), + onConfirm: jest.fn(), + }; + + beforeEach(() => { + appMock = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders correctly', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions-confirm-modal')).toBeInTheDocument(); + expect(result.getByText('My title')).toBeInTheDocument(); + expect(result.getByText('My button text')).toBeInTheDocument(); + expect(result.getByText('Cancel')).toBeInTheDocument(); + }); + + it('calls onConfirm', async () => { + const result = appMock.render(); + + expect(result.getByText('My button text')).toBeInTheDocument(); + userEvent.click(result.getByText('My button text')); + + expect(props.onConfirm).toHaveBeenCalled(); + }); + + it('calls onCancel', async () => { + const result = appMock.render(); + + expect(result.getByText('Cancel')).toBeInTheDocument(); + userEvent.click(result.getByText('Cancel')); + + expect(props.onCancel).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.tsx b/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.tsx new file mode 100644 index 0000000000000..be97b65669287 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/delete_attachment_confirmation_modal.tsx @@ -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 React from 'react'; +import type { EuiConfirmModalProps } from '@elastic/eui'; +import { EuiConfirmModal } from '@elastic/eui'; +import { CANCEL_BUTTON } from './property_actions/translations'; + +type Pros = Pick; + +const DeleteAttachmentConfirmationModalComponent: React.FC = ({ + title, + confirmButtonText, + onConfirm, + onCancel, +}) => { + return ( + + ); +}; + +DeleteAttachmentConfirmationModalComponent.displayName = 'DeleteAttachmentConfirmationModal'; + +export const DeleteAttachmentConfirmationModal = React.memo( + DeleteAttachmentConfirmationModalComponent +); diff --git a/x-pack/plugins/cases/public/components/user_actions/description.test.tsx b/x-pack/plugins/cases/public/components/user_actions/description.test.tsx index d76829add5a63..9fddcbb3cce6a 100644 --- a/x-pack/plugins/cases/public/components/user_actions/description.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/description.test.tsx @@ -7,24 +7,96 @@ import React from 'react'; import { EuiCommentList } from '@elastic/eui'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { Actions } from '../../../common/api'; import { getUserAction } from '../../containers/mock'; import { TestProviders } from '../../common/mock'; -import { createDescriptionUserActionBuilder } from './description'; +import { createDescriptionUserActionBuilder, getDescriptionUserAction } from './description'; import { getMockBuilderArgs } from './mock'; +import userEvent from '@testing-library/user-event'; +import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; jest.mock('../../common/lib/kibana'); jest.mock('../../common/navigation/hooks'); describe('createDescriptionUserActionBuilder ', () => { + const onUpdateField = jest.fn(); const builderArgs = getMockBuilderArgs(); beforeEach(() => { jest.clearAllMocks(); }); + it('renders correctly description', async () => { + const descriptionUserAction = getDescriptionUserAction({ + ...builderArgs, + onUpdateField, + isLoadingDescription: false, + }); + + render( + + + + ); + + expect(screen.getByText('added description')).toBeInTheDocument(); + expect(screen.getByText('Security banana Issue')).toBeInTheDocument(); + }); + + it('edits the description correctly', async () => { + const descriptionUserAction = getDescriptionUserAction({ + ...builderArgs, + onUpdateField, + isLoadingDescription: false, + }); + + const res = render( + + + + ); + + expect(res.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(res.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(res.queryByTestId('property-actions-pencil')).toBeInTheDocument(); + userEvent.click(res.getByTestId('property-actions-pencil')); + + await waitFor(() => { + expect(builderArgs.handleManageMarkdownEditId).toHaveBeenCalledWith('description'); + }); + }); + + it('quotes the description correctly', async () => { + const descriptionUserAction = getDescriptionUserAction({ + ...builderArgs, + onUpdateField, + isLoadingDescription: false, + }); + + const res = render( + + + + ); + + expect(res.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(res.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(res.queryByTestId('property-actions-quote')).toBeInTheDocument(); + userEvent.click(res.getByTestId('property-actions-quote')); + + await waitFor(() => { + expect(builderArgs.handleManageQuote).toHaveBeenCalledWith('Security banana Issue'); + }); + }); + it('renders correctly when editing a description', async () => { const userAction = getUserAction('description', Actions.update); const builder = createDescriptionUserActionBuilder({ diff --git a/x-pack/plugins/cases/public/components/user_actions/description.tsx b/x-pack/plugins/cases/public/components/user_actions/description.tsx index d37992577aeb8..19874dc05f2d0 100644 --- a/x-pack/plugins/cases/public/components/user_actions/description.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/description.tsx @@ -17,6 +17,7 @@ import { UserActionMarkdown } from './markdown_form'; import * as i18n from './translations'; import { HoverableAvatarResolver } from '../user_profiles/hoverable_avatar_resolver'; import { HoverableUsernameResolver } from '../user_profiles/hoverable_username_resolver'; +import { DescriptionPropertyActions } from './property_actions/description_property_actions'; const DESCRIPTION_ID = 'description'; @@ -69,15 +70,13 @@ export const getDescriptionUserAction = ({ isEdit: manageMarkdownEditIds.includes(DESCRIPTION_ID), }), actions: ( - + + handleManageMarkdownEditId(DESCRIPTION_ID)} + onQuote={() => handleManageQuote(caseData.description)} + /> + ), }; }; diff --git a/x-pack/plugins/cases/public/components/user_actions/index.test.tsx b/x-pack/plugins/cases/public/components/user_actions/index.test.tsx index 3566de547d354..1a643234d3456 100644 --- a/x-pack/plugins/cases/public/components/user_actions/index.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/index.test.tsx @@ -18,7 +18,6 @@ import { getUserAction, getHostIsolationUserAction, hostIsolationComment, - hostReleaseComment, } from '../../containers/mock'; import { UserActions } from '.'; import type { AppMockRenderer } from '../../common/mock'; @@ -424,45 +423,5 @@ describe(`UserActions`, () => { expect(screen.getByText('DR')).toBeInTheDocument(); expect(screen.getByText('Damaged Raccoon')).toBeInTheDocument(); }); - - it('shows a lock icon if the action is isolate', async () => { - const isolateAction = [getHostIsolationUserAction()]; - const props = { - ...defaultProps, - caseUserActions: isolateAction, - data: { ...defaultProps.data, comments: [hostIsolationComment()] }, - }; - - const wrapper = mount( - - - - ); - await waitFor(() => { - expect( - wrapper.find(`[data-test-subj="endpoint-action-icon"]`).first().prop('iconType') - ).toBe('lock'); - }); - }); - - it('shows a lockOpen icon if the action is unisolate/release', async () => { - const isolateAction = [getHostIsolationUserAction()]; - const props = { - ...defaultProps, - caseUserActions: isolateAction, - data: { ...defaultProps.data, comments: [hostReleaseComment()] }, - }; - - const wrapper = mount( - - - - ); - await waitFor(() => { - expect( - wrapper.find(`[data-test-subj="endpoint-action-icon"]`).first().prop('iconType') - ).toBe('lockOpen'); - }); - }); }); }); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions.test.tsx deleted file mode 100644 index 75d2b3027f37b..0000000000000 --- a/x-pack/plugins/cases/public/components/user_actions/property_actions.test.tsx +++ /dev/null @@ -1,251 +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 React from 'react'; -import type { UserActionPropertyActionsProps } from './property_actions'; -import { UserActionPropertyActions } from './property_actions'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; -import { - noCreateCasesPermissions, - noDeleteCasesPermissions, - noUpdateCasesPermissions, - TestProviders, -} from '../../common/mock'; - -jest.mock('../../common/lib/kibana'); - -const onEdit = jest.fn(); -const onQuote = jest.fn(); -const props = { - commentMarkdown: '', - id: 'property-actions-id', - editLabel: 'edit', - quoteLabel: 'quote', - disabled: false, - isLoading: false, - onEdit, - onQuote, -}; - -describe('UserActionPropertyActions ', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('renders', async () => { - render( - - - - ); - - expect(screen.queryByTestId('user-action-title-loading')).not.toBeInTheDocument(); - expect(screen.getByTestId('property-actions')).toBeInTheDocument(); - }); - - it('shows the edit and quote buttons', async () => { - const renderResult = render( - - - - ); - - userEvent.click(renderResult.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - expect(screen.getByTestId('property-actions-pencil')).toBeInTheDocument(); - expect(screen.getByTestId('property-actions-quote')).toBeInTheDocument(); - }); - - it('quote click calls onQuote', async () => { - const renderResult = render( - - - - ); - - userEvent.click(renderResult.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - userEvent.click(renderResult.getByTestId('property-actions-quote')); - - expect(onQuote).toHaveBeenCalledWith(props.id); - }); - - it('pencil click calls onEdit', async () => { - const renderResult = render( - - - - ); - - userEvent.click(renderResult.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - userEvent.click(renderResult.getByTestId('property-actions-pencil')); - expect(onEdit).toHaveBeenCalledWith(props.id); - }); - - it('shows the spinner when loading', async () => { - render( - - - - ); - expect(screen.getByTestId('user-action-title-loading')).toBeInTheDocument(); - expect(screen.queryByTestId('property-actions')).not.toBeInTheDocument(); - }); - - describe('deletion props', () => { - let onDelete: jest.Mock; - let deleteProps: UserActionPropertyActionsProps; - - beforeEach(() => { - jest.clearAllMocks(); - - onDelete = jest.fn(); - deleteProps = { - ...props, - onDelete, - deleteLabel: 'delete me', - deleteConfirmTitle: 'confirm delete me', - }; - }); - - it('does not show the delete icon when the user does not have delete permissions', async () => { - const renderResult = render( - - - - ); - - userEvent.click(renderResult.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - expect(renderResult.queryByTestId('property-actions-trash')).not.toBeInTheDocument(); - expect(renderResult.queryByTestId('property-actions-pencil')).toBeInTheDocument(); - expect(renderResult.queryByTestId('property-actions-quote')).toBeInTheDocument(); - }); - - it('does not show the pencil icon when the user does not have update permissions', async () => { - const renderResult = render( - - - - ); - - userEvent.click(renderResult.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - expect(renderResult.queryByTestId('property-actions-trash')).toBeInTheDocument(); - expect(renderResult.queryByTestId('property-actions-pencil')).not.toBeInTheDocument(); - expect(renderResult.queryByTestId('property-actions-quote')).toBeInTheDocument(); - }); - - it('does not show the quote icon when the user does not have create permissions', async () => { - const renderResult = render( - - - - ); - - userEvent.click(renderResult.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - expect(renderResult.queryByTestId('property-actions-trash')).toBeInTheDocument(); - expect(renderResult.queryByTestId('property-actions-pencil')).toBeInTheDocument(); - expect(renderResult.queryByTestId('property-actions-quote')).not.toBeInTheDocument(); - }); - - it('shows the delete button', () => { - const renderResult = render( - - - - ); - - userEvent.click(renderResult.getByTestId('property-actions-ellipses')); - expect(renderResult.getByTestId('property-actions-trash')).toBeTruthy(); - }); - - it('shows a confirm dialog when the delete button is clicked', async () => { - const renderResult = render( - - - - ); - - userEvent.click(renderResult.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - userEvent.click(renderResult.getByTestId('property-actions-trash')); - - expect(renderResult.getByTestId('property-actions-confirm-modal')).toBeTruthy(); - }); - - it('closes the confirm dialog when the cancel button is clicked', async () => { - const renderResult = render( - - - - ); - - userEvent.click(renderResult.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - userEvent.click(renderResult.getByTestId('property-actions-trash')); - expect(renderResult.getByTestId('property-actions-confirm-modal')).toBeTruthy(); - - userEvent.click(renderResult.getByTestId('confirmModalCancelButton')); - expect(renderResult.queryByTestId('property-actions-confirm-modal')).toBe(null); - }); - - it('calls onDelete when the confirm is pressed', async () => { - const renderResult = render( - - - - ); - - userEvent.click(renderResult.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - userEvent.click(renderResult.getByTestId('property-actions-trash')); - expect(renderResult.getByTestId('property-actions-confirm-modal')).toBeTruthy(); - - userEvent.click(renderResult.getByTestId('confirmModalConfirmButton')); - expect(onDelete).toHaveBeenCalledWith(deleteProps.id); - }); - }); - - describe('action filtering', () => { - const tests = [ - ['edit', 'pencil'], - ['delete', 'trash'], - ['quote', 'quote'], - ] as const; - - it.each(tests)('renders action %s', async (action, type) => { - const renderResult = render( - - {}} - deleteLabel={'test'} - actions={[action]} - /> - - ); - - expect(renderResult.queryByTestId('user-action-title-loading')).not.toBeInTheDocument(); - expect(renderResult.getByTestId('property-actions')).toBeInTheDocument(); - - userEvent.click(renderResult.getByTestId('property-actions-ellipses')); - await waitForEuiPopoverOpen(); - - expect(renderResult.queryByTestId(`property-actions-${type}`)).toBeInTheDocument(); - /** - * This check ensures that no other action is rendered. There is - * one button to open the popover and one button for the action - **/ - expect(await renderResult.findAllByRole('button')).toHaveLength(2); - }); - }); -}); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions.tsx deleted file mode 100644 index 132b824109e51..0000000000000 --- a/x-pack/plugins/cases/public/components/user_actions/property_actions.tsx +++ /dev/null @@ -1,164 +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 { noop } from 'lodash'; -import React, { memo, useMemo, useCallback, useState } from 'react'; -import { EuiConfirmModal, EuiLoadingSpinner } from '@elastic/eui'; - -import { PropertyActions } from '../property_actions'; -import { useLensOpenVisualization } from '../markdown_editor/plugins/lens/use_lens_open_visualization'; -import { CANCEL_BUTTON, CONFIRM_BUTTON } from './translations'; -import { useCasesContext } from '../cases_context/use_cases_context'; - -const totalActions = { - edit: 'edit', - delete: 'delete', - quote: 'quote', - showLensEditor: 'showLensEditor', -} as const; - -const availableActions = Object.keys(totalActions) as Array; - -export type Actions = typeof availableActions; - -export interface UserActionPropertyActionsProps { - id: string; - actions?: Actions; - editLabel?: string; - deleteLabel?: string; - deleteConfirmTitle?: string; - quoteLabel?: string; - isLoading: boolean; - onEdit?: (id: string) => void; - onDelete?: (id: string) => void; - onQuote?: (id: string) => void; - commentMarkdown?: string; -} - -const UserActionPropertyActionsComponent = ({ - id, - actions = availableActions, - editLabel = '', - quoteLabel = '', - deleteLabel = '', - deleteConfirmTitle, - isLoading, - onEdit = noop, - onDelete, - onQuote = noop, - commentMarkdown, -}: UserActionPropertyActionsProps) => { - const { permissions } = useCasesContext(); - const { canUseEditor, actionConfig } = useLensOpenVisualization({ - comment: commentMarkdown ?? '', - }); - const onEditClick = useCallback(() => onEdit(id), [id, onEdit]); - const onQuoteClick = useCallback(() => onQuote(id), [id, onQuote]); - const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); - - const onDeleteClick = useCallback(() => { - setShowDeleteConfirm(true); - }, []); - - const onDeleteConfirmClick = useCallback(() => { - if (onDelete) { - onDelete(id); - } - setShowDeleteConfirm(false); - }, [id, onDelete]); - - const onDeleteCancelClick = useCallback(() => { - setShowDeleteConfirm(false); - }, []); - - const propertyActions = useMemo(() => { - const showEditPencilIcon = permissions.update && actions.includes(totalActions.edit); - - const showTrashIcon = Boolean( - permissions.delete && deleteLabel && onDelete && actions.includes(totalActions.delete) - ); - - const showQuoteIcon = permissions.create && actions.includes(totalActions.quote); - - const showLensEditor = - permissions.update && - canUseEditor && - actionConfig && - actions.includes(totalActions.showLensEditor); - - return [ - ...(showEditPencilIcon - ? [ - { - iconType: 'pencil', - label: editLabel, - onClick: onEditClick, - }, - ] - : []), - ...(showTrashIcon - ? [ - { - iconType: 'trash', - label: deleteLabel, - onClick: onDeleteClick, - }, - ] - : []), - ...(showQuoteIcon - ? [ - { - iconType: 'quote', - label: quoteLabel, - onClick: onQuoteClick, - }, - ] - : []), - ...(showLensEditor ? [actionConfig] : []), - ]; - }, [ - permissions.update, - permissions.delete, - permissions.create, - actions, - deleteLabel, - onDelete, - canUseEditor, - actionConfig, - editLabel, - onEditClick, - onDeleteClick, - quoteLabel, - onQuoteClick, - ]); - - if (!propertyActions.length) { - return null; - } - - return ( - <> - {isLoading && } - {!isLoading && } - {showDeleteConfirm ? ( - - ) : null} - - ); -}; -UserActionPropertyActionsComponent.displayName = 'UserActionPropertyActions'; - -export const UserActionPropertyActions = memo(UserActionPropertyActionsComponent); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/alert_property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/alert_property_actions.test.tsx new file mode 100644 index 0000000000000..dc8a57b8477f6 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/alert_property_actions.test.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; +import { waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { AppMockRenderer } from '../../../common/mock'; +import { + noCasesPermissions, + onlyDeleteCasesPermission, + createAppMockRenderer, +} from '../../../common/mock'; +import { AlertPropertyActions } from './alert_property_actions'; + +describe('AlertPropertyActions', () => { + let appMock: AppMockRenderer; + + const props = { + isLoading: false, + totalAlerts: 1, + onDelete: jest.fn(), + }; + + beforeEach(() => { + appMock = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders the correct number of actions', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.getByTestId('property-actions-group').children.length).toBe(1); + expect(result.queryByTestId('property-actions-minusInCircle')).toBeInTheDocument(); + }); + + it('renders the modal info correctly for one alert', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-minusInCircle')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-minusInCircle')); + + await waitFor(() => { + expect(result.queryByTestId('property-actions-confirm-modal')).toBeInTheDocument(); + }); + + expect(result.getByTestId('confirmModalTitleText')).toHaveTextContent('Remove alert'); + expect(result.getByText('Remove')).toBeInTheDocument(); + }); + + it('renders the modal info correctly for multiple alert', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-minusInCircle')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-minusInCircle')); + + await waitFor(() => { + expect(result.queryByTestId('property-actions-confirm-modal')).toBeInTheDocument(); + }); + + expect(result.getByTestId('confirmModalTitleText')).toHaveTextContent('Remove alerts'); + expect(result.getByText('Remove')).toBeInTheDocument(); + }); + + it('remove alerts correctly', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-minusInCircle')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-minusInCircle')); + + await waitFor(() => { + expect(result.queryByTestId('property-actions-confirm-modal')).toBeInTheDocument(); + }); + + userEvent.click(result.getByText('Remove')); + expect(props.onDelete).toHaveBeenCalled(); + }); + + it('does not show the property actions without delete permissions', async () => { + appMock = createAppMockRenderer({ permissions: noCasesPermissions() }); + const result = appMock.render(); + + expect(result.queryByTestId('property-actions')).not.toBeInTheDocument(); + }); + + it('does show the property actions with only delete permissions', async () => { + appMock = createAppMockRenderer({ permissions: onlyDeleteCasesPermission() }); + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/alert_property_actions.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/alert_property_actions.tsx new file mode 100644 index 0000000000000..0acafdcfa8d60 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/alert_property_actions.tsx @@ -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 React, { useMemo } from 'react'; +import { useCasesContext } from '../../cases_context/use_cases_context'; +import { DeleteAttachmentConfirmationModal } from '../delete_attachment_confirmation_modal'; +import { UserActionPropertyActions } from './property_actions'; +import * as i18n from './translations'; +import { useDeletePropertyAction } from './use_delete_property_action'; + +interface Props { + isLoading: boolean; + totalAlerts: number; + onDelete: () => void; +} + +const AlertPropertyActionsComponent: React.FC = ({ isLoading, totalAlerts, onDelete }) => { + const { permissions } = useCasesContext(); + const { showDeletionModal, onModalOpen, onConfirm, onCancel } = useDeletePropertyAction({ + onDelete, + }); + + const propertyActions = useMemo(() => { + const showRemoveAlertIcon = permissions.delete; + + return [ + ...(showRemoveAlertIcon + ? [ + { + iconType: 'minusInCircle', + label: i18n.REMOVE_ALERTS(totalAlerts), + onClick: onModalOpen, + }, + ] + : []), + ]; + }, [permissions.delete, totalAlerts, onModalOpen]); + + return ( + <> + + {showDeletionModal ? ( + + ) : null} + + ); +}; + +AlertPropertyActionsComponent.displayName = 'AlertPropertyActions'; + +export const AlertPropertyActions = React.memo(AlertPropertyActionsComponent); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/description_property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/description_property_actions.test.tsx new file mode 100644 index 0000000000000..bfaa349caf46f --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/description_property_actions.test.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; +import userEvent from '@testing-library/user-event'; +import type { AppMockRenderer } from '../../../common/mock'; +import { createAppMockRenderer, noCasesPermissions } from '../../../common/mock'; +import { DescriptionPropertyActions } from './description_property_actions'; + +describe('DescriptionPropertyActions', () => { + let appMock: AppMockRenderer; + + const props = { + isLoading: false, + onEdit: jest.fn(), + onQuote: jest.fn(), + }; + + beforeEach(() => { + appMock = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders the correct number of actions', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.getByTestId('property-actions-group').children.length).toBe(2); + expect(result.queryByTestId('property-actions-pencil')).toBeInTheDocument(); + expect(result.queryByTestId('property-actions-quote')).toBeInTheDocument(); + }); + + it('edits the description correctly', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-pencil')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-pencil')); + + expect(props.onEdit).toHaveBeenCalled(); + }); + + it('quotes the description correctly', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-quote')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-quote')); + + expect(props.onQuote).toHaveBeenCalled(); + }); + + it('does not show the property actions without permissions', async () => { + appMock = createAppMockRenderer({ permissions: noCasesPermissions() }); + const result = appMock.render(); + + expect(result.queryByTestId('property-actions')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/description_property_actions.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/description_property_actions.tsx new file mode 100644 index 0000000000000..5ef72a5590140 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/description_property_actions.tsx @@ -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 React, { useMemo } from 'react'; +import { useCasesContext } from '../../cases_context/use_cases_context'; +import * as i18n from './translations'; +import { UserActionPropertyActions } from './property_actions'; + +interface Props { + isLoading: boolean; + onEdit: () => void; + onQuote: () => void; +} + +const DescriptionPropertyActionsComponent: React.FC = ({ isLoading, onEdit, onQuote }) => { + const { permissions } = useCasesContext(); + + const propertyActions = useMemo(() => { + const showEditPencilIcon = permissions.update; + const showQuoteIcon = permissions.create; + + return [ + ...(showEditPencilIcon + ? [ + { + iconType: 'pencil', + label: i18n.EDIT_DESCRIPTION, + onClick: onEdit, + }, + ] + : []), + ...(showQuoteIcon + ? [ + { + iconType: 'quote', + label: i18n.QUOTE, + onClick: onQuote, + }, + ] + : []), + ]; + }, [permissions.update, permissions.create, onEdit, onQuote]); + + return ; +}; + +DescriptionPropertyActionsComponent.displayName = 'DescriptionPropertyActions'; + +export const DescriptionPropertyActions = React.memo(DescriptionPropertyActionsComponent); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/property_actions.test.tsx new file mode 100644 index 0000000000000..c7cfdb25bb359 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/property_actions.test.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; +import userEvent from '@testing-library/user-event'; +import type { AppMockRenderer } from '../../../common/mock'; +import { createAppMockRenderer } from '../../../common/mock'; +import { UserActionPropertyActions } from './property_actions'; + +describe('UserActionPropertyActions', () => { + let appMock: AppMockRenderer; + const onClick = jest.fn(); + + const props = { + isLoading: false, + propertyActions: [ + { + iconType: 'pencil', + label: 'Edit', + onClick, + }, + ], + }; + + beforeEach(() => { + appMock = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders the loading spinner correctly when loading', async () => { + const result = appMock.render(); + + expect(result.getByTestId('user-action-title-loading')).toBeInTheDocument(); + expect(result.queryByTestId('property-actions')).not.toBeInTheDocument(); + }); + + it('renders the property actions', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.getByTestId('property-actions-group').children.length).toBe(1); + expect(result.queryByTestId('property-actions-pencil')).toBeInTheDocument(); + }); + + it('does not render if properties are empty', async () => { + const result = appMock.render( + + ); + + expect(result.queryByTestId('property-actions')).not.toBeInTheDocument(); + expect(result.queryByTestId('user-action-title-loading')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/property_actions.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/property_actions.tsx new file mode 100644 index 0000000000000..abf897404711a --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/property_actions.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 { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import React from 'react'; +import type { PropertyActionButtonProps } from '../../property_actions'; +import { PropertyActions } from '../../property_actions'; + +interface Props { + isLoading: boolean; + propertyActions: PropertyActionButtonProps[]; +} + +const UserActionPropertyActionsComponent: React.FC = ({ isLoading, propertyActions }) => { + if (propertyActions.length === 0) { + return null; + } + + return ( + + {isLoading ? ( + + ) : ( + + )} + + ); +}; + +UserActionPropertyActionsComponent.displayName = 'UserActionPropertyActions'; + +export const UserActionPropertyActions = React.memo(UserActionPropertyActionsComponent); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.test.tsx new file mode 100644 index 0000000000000..a756f43893e03 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.test.tsx @@ -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 React from 'react'; +import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; +import { waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import type { AppMockRenderer } from '../../../common/mock'; +import { + noCasesPermissions, + onlyDeleteCasesPermission, + createAppMockRenderer, +} from '../../../common/mock'; +import { RegisteredAttachmentsPropertyActions } from './registered_attachments_property_actions'; + +describe('RegisteredAttachmentsPropertyActions', () => { + let appMock: AppMockRenderer; + + const props = { + isLoading: false, + onDelete: jest.fn(), + }; + + beforeEach(() => { + appMock = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders the correct number of actions', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.getByTestId('property-actions-group').children.length).toBe(1); + expect(result.queryByTestId('property-actions-trash')).toBeInTheDocument(); + }); + + it('renders the modal info correctly', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-trash')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-trash')); + + await waitFor(() => { + expect(result.queryByTestId('property-actions-confirm-modal')).toBeInTheDocument(); + }); + + expect(result.getByTestId('confirmModalTitleText')).toHaveTextContent('Delete attachment'); + expect(result.getByText('Delete')).toBeInTheDocument(); + }); + + it('remove attachments correctly', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-trash')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-trash')); + + await waitFor(() => { + expect(result.queryByTestId('property-actions-confirm-modal')).toBeInTheDocument(); + }); + + userEvent.click(result.getByText('Delete')); + expect(props.onDelete).toHaveBeenCalled(); + }); + + it('does not show the property actions without delete permissions', async () => { + appMock = createAppMockRenderer({ permissions: noCasesPermissions() }); + const result = appMock.render(); + + expect(result.queryByTestId('property-actions')).not.toBeInTheDocument(); + }); + + it('does show the property actions with only delete permissions', async () => { + appMock = createAppMockRenderer({ permissions: onlyDeleteCasesPermission() }); + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.tsx new file mode 100644 index 0000000000000..28d82525246f5 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.tsx @@ -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 React, { useMemo } from 'react'; +import { useCasesContext } from '../../cases_context/use_cases_context'; +import * as i18n from './translations'; +import { UserActionPropertyActions } from './property_actions'; +import { DeleteAttachmentConfirmationModal } from '../delete_attachment_confirmation_modal'; +import { useDeletePropertyAction } from './use_delete_property_action'; + +interface Props { + isLoading: boolean; + onDelete: () => void; +} + +const RegisteredAttachmentsPropertyActionsComponent: React.FC = ({ + isLoading, + onDelete, +}) => { + const { permissions } = useCasesContext(); + const { showDeletionModal, onModalOpen, onConfirm, onCancel } = useDeletePropertyAction({ + onDelete, + }); + + const propertyActions = useMemo(() => { + const showTrashIcon = permissions.delete; + + return [ + ...(showTrashIcon + ? [ + { + iconType: 'trash', + label: i18n.DELETE_ATTACHMENT, + onClick: onModalOpen, + }, + ] + : []), + ]; + }, [permissions.delete, onModalOpen]); + + return ( + <> + + {showDeletionModal ? ( + + ) : null} + + ); +}; + +RegisteredAttachmentsPropertyActionsComponent.displayName = 'RegisteredAttachmentsPropertyActions'; + +export const RegisteredAttachmentsPropertyActions = React.memo( + RegisteredAttachmentsPropertyActionsComponent +); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/translations.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/translations.tsx new file mode 100644 index 0000000000000..fd54ac491bc7a --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/translations.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +export * from '../translations'; + +export const DELETE_ATTACHMENT = i18n.translate('xpack.cases.userActions.deleteAttachment', { + defaultMessage: 'Delete attachment', +}); + +export const REMOVE_ALERTS = (totalAlerts: number): string => + i18n.translate('xpack.cases.caseView.alerts.removeAlerts', { + values: { totalAlerts }, + defaultMessage: 'Remove {totalAlerts, plural, =1 {alert} other {alerts}}', + }); + +export const REMOVE = i18n.translate('xpack.cases.caseView.alerts.remove', { + defaultMessage: 'Remove', +}); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/use_delete_property_action.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/use_delete_property_action.test.tsx new file mode 100644 index 0000000000000..2db865ee3b22b --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/use_delete_property_action.test.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook, act } from '@testing-library/react-hooks'; +import type { AppMockRenderer } from '../../../common/mock'; +import { createAppMockRenderer } from '../../../common/mock'; +import { useDeletePropertyAction } from './use_delete_property_action'; + +describe('UserActionPropertyActions', () => { + let appMockRender: AppMockRenderer; + const onDelete = jest.fn(); + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('init', async () => { + const { result } = renderHook(() => useDeletePropertyAction({ onDelete }), { + wrapper: appMockRender.AppWrapper, + }); + + expect(result.current.showDeletionModal).toBe(false); + }); + + it('opens the modal', async () => { + const { result } = renderHook(() => useDeletePropertyAction({ onDelete }), { + wrapper: appMockRender.AppWrapper, + }); + + act(() => { + result.current.onModalOpen(); + }); + + expect(result.current.showDeletionModal).toBe(true); + }); + + it('closes the modal', async () => { + const { result } = renderHook(() => useDeletePropertyAction({ onDelete }), { + wrapper: appMockRender.AppWrapper, + }); + + act(() => { + result.current.onModalOpen(); + }); + + expect(result.current.showDeletionModal).toBe(true); + + act(() => { + result.current.onCancel(); + }); + + expect(result.current.showDeletionModal).toBe(false); + }); + + it('calls onDelete on confirm', async () => { + const { result } = renderHook(() => useDeletePropertyAction({ onDelete }), { + wrapper: appMockRender.AppWrapper, + }); + + act(() => { + result.current.onModalOpen(); + }); + + expect(result.current.showDeletionModal).toBe(true); + + act(() => { + result.current.onConfirm(); + }); + + expect(result.current.showDeletionModal).toBe(false); + expect(onDelete).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/use_delete_property_action.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/use_delete_property_action.tsx new file mode 100644 index 0000000000000..4045beff6347d --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/use_delete_property_action.tsx @@ -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 { useCallback, useState } from 'react'; + +interface Props { + onDelete: () => void; +} + +export const useDeletePropertyAction = ({ onDelete }: Props) => { + const [showDeletionModal, setShowDeletionModal] = useState(false); + + const onModalOpen = useCallback(() => { + setShowDeletionModal(true); + }, []); + + const onConfirm = useCallback(() => { + setShowDeletionModal(false); + onDelete(); + }, [onDelete]); + + const onCancel = useCallback(() => { + setShowDeletionModal(false); + }, []); + + return { showDeletionModal, onModalOpen, onConfirm, onCancel }; +}; diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/user_comment_property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/user_comment_property_actions.test.tsx new file mode 100644 index 0000000000000..557dae707c20f --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/user_comment_property_actions.test.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 React from 'react'; +import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; +import userEvent from '@testing-library/user-event'; +import type { AppMockRenderer } from '../../../common/mock'; +import { + noCasesPermissions, + onlyDeleteCasesPermission, + createAppMockRenderer, +} from '../../../common/mock'; +import { UserCommentPropertyActions } from './user_comment_property_actions'; +import { waitFor } from '@testing-library/react'; + +describe('UserCommentPropertyActions', () => { + let appMock: AppMockRenderer; + + const props = { + isLoading: false, + onEdit: jest.fn(), + onQuote: jest.fn(), + onDelete: jest.fn(), + }; + + beforeEach(() => { + appMock = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('renders the correct number of actions', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.getByTestId('property-actions-group').children.length).toBe(3); + expect(result.queryByTestId('property-actions-pencil')).toBeInTheDocument(); + expect(result.queryByTestId('property-actions-trash')).toBeInTheDocument(); + expect(result.queryByTestId('property-actions-quote')).toBeInTheDocument(); + }); + + it('edits the comment correctly', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-pencil')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-pencil')); + + expect(props.onEdit).toHaveBeenCalled(); + }); + + it('quotes the comment correctly', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-quote')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-quote')); + + expect(props.onQuote).toHaveBeenCalled(); + }); + + it('deletes the comment correctly', async () => { + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-ellipses')); + await waitForEuiPopoverOpen(); + + expect(result.queryByTestId('property-actions-trash')).toBeInTheDocument(); + + userEvent.click(result.getByTestId('property-actions-trash')); + + await waitFor(() => { + expect(result.queryByTestId('property-actions-confirm-modal')).toBeInTheDocument(); + }); + + userEvent.click(result.getByText('Delete')); + expect(props.onDelete).toHaveBeenCalled(); + }); + + it('does not show the property actions without delete permissions', async () => { + appMock = createAppMockRenderer({ permissions: noCasesPermissions() }); + const result = appMock.render(); + + expect(result.queryByTestId('property-actions')).not.toBeInTheDocument(); + }); + + it('does show the property actions with only delete permissions', async () => { + appMock = createAppMockRenderer({ permissions: onlyDeleteCasesPermission() }); + const result = appMock.render(); + + expect(result.getByTestId('property-actions')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/user_comment_property_actions.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/user_comment_property_actions.tsx new file mode 100644 index 0000000000000..90d16dfc2bf61 --- /dev/null +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/user_comment_property_actions.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, { useMemo } from 'react'; +import { useCasesContext } from '../../cases_context/use_cases_context'; +import { useLensOpenVisualization } from '../../markdown_editor/plugins/lens/use_lens_open_visualization'; +import * as i18n from './translations'; +import { UserActionPropertyActions } from './property_actions'; +import { DeleteAttachmentConfirmationModal } from '../delete_attachment_confirmation_modal'; +import { useDeletePropertyAction } from './use_delete_property_action'; + +interface Props { + isLoading: boolean; + commentContent?: string; + onEdit: () => void; + onDelete: () => void; + onQuote: () => void; +} + +const UserCommentPropertyActionsComponent: React.FC = ({ + isLoading, + commentContent, + onEdit, + onDelete, + onQuote, +}) => { + const { permissions } = useCasesContext(); + const { showDeletionModal, onModalOpen, onConfirm, onCancel } = useDeletePropertyAction({ + onDelete, + }); + + const { canUseEditor, actionConfig } = useLensOpenVisualization({ + comment: commentContent ?? '', + }); + + const propertyActions = useMemo(() => { + const showEditPencilIcon = permissions.update; + const showTrashIcon = permissions.delete; + const showQuoteIcon = permissions.create; + + const showLensEditor = permissions.update && canUseEditor && actionConfig; + + return [ + ...(showEditPencilIcon + ? [ + { + iconType: 'pencil', + label: i18n.EDIT_COMMENT, + onClick: onEdit, + }, + ] + : []), + ...(showTrashIcon + ? [ + { + iconType: 'trash', + label: i18n.DELETE_COMMENT, + onClick: onModalOpen, + }, + ] + : []), + ...(showQuoteIcon + ? [ + { + iconType: 'quote', + label: i18n.QUOTE, + onClick: onQuote, + }, + ] + : []), + ...(showLensEditor ? [actionConfig] : []), + ]; + }, [ + permissions.update, + permissions.delete, + permissions.create, + canUseEditor, + actionConfig, + onEdit, + onModalOpen, + onQuote, + ]); + + return ( + <> + + {showDeletionModal ? ( + + ) : null} + + ); +}; + +UserCommentPropertyActionsComponent.displayName = 'UserCommentPropertyActions'; + +export const UserCommentPropertyActions = React.memo(UserCommentPropertyActionsComponent); diff --git a/x-pack/plugins/cases/public/components/user_actions/translations.ts b/x-pack/plugins/cases/public/components/user_actions/translations.ts index 91425c368286d..28531421c7703 100644 --- a/x-pack/plugins/cases/public/components/user_actions/translations.ts +++ b/x-pack/plugins/cases/public/components/user_actions/translations.ts @@ -75,7 +75,7 @@ export const CANCEL_BUTTON = i18n.translate('xpack.cases.caseView.delete.cancel' defaultMessage: 'Cancel', }); -export const CONFIRM_BUTTON = i18n.translate('xpack.cases.caseView.delete.confirm', { +export const DELETE = i18n.translate('xpack.cases.caseView.delete.confirm', { defaultMessage: 'Delete', }); diff --git a/x-pack/plugins/cases/public/components/user_actions/types.ts b/x-pack/plugins/cases/public/components/user_actions/types.ts index 978de436b377e..c6bb80b274f39 100644 --- a/x-pack/plugins/cases/public/components/user_actions/types.ts +++ b/x-pack/plugins/cases/public/components/user_actions/types.ts @@ -58,13 +58,13 @@ export interface UserActionBuilderArgs { loadingCommentIds: string[]; loadingAlertData: boolean; alertData: Record; + actionsNavigation?: ActionsNavigation; handleOutlineComment: (id: string) => void; handleManageMarkdownEditId: (id: string) => void; handleSaveComment: ({ id, version }: { id: string; version: string }, content: string) => void; handleDeleteComment: (id: string) => void; handleManageQuote: (quote: string) => void; onShowAlertDetails: (alertId: string, index: string) => void; - actionsNavigation?: ActionsNavigation; getRuleDetailsHref?: RuleDetailsNavigation['href']; onRuleDetailsClick?: RuleDetailsNavigation['onClick']; } diff --git a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx index 510be840d5436..bb663dd89bd10 100644 --- a/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/use_user_actions_handler.test.tsx @@ -28,7 +28,7 @@ const openLensModal = jest.fn(); describe('useUserActionsHandler', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); jest.spyOn(global, 'setTimeout'); }); diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts index cada10b9eb109..cdaa9d56aea96 100644 --- a/x-pack/plugins/cases/public/containers/mock.ts +++ b/x-pack/plugins/cases/public/containers/mock.ts @@ -775,9 +775,9 @@ export const getJiraConnector = (overrides?: Partial): CaseConnec export const jiraFields = { fields: { issueType: '10006', priority: null, parent: null } }; -export const getAlertUserAction = (): SnakeToCamelCase< - UserActionWithResponse -> => ({ +export const getAlertUserAction = ( + overrides?: Record +): SnakeToCamelCase> => ({ ...getUserAction(ActionTypes.comment, Actions.create), actionId: 'alert-action-id', commentId: 'alert-comment-id', @@ -794,6 +794,29 @@ export const getAlertUserAction = (): SnakeToCamelCase< }, }, }, + ...overrides, +}); + +export const getMultipleAlertsUserAction = ( + overrides?: Record +): SnakeToCamelCase> => ({ + ...getUserAction(ActionTypes.comment, Actions.create), + actionId: 'alert-action-id', + commentId: 'alert-comment-id', + type: ActionTypes.comment, + payload: { + comment: { + type: CommentType.alert, + alertId: ['alert-id-1', 'alert-id-2'], + index: ['index-id-1', 'index-id-2'], + owner: SECURITY_SOLUTION_OWNER, + rule: { + id: 'rule-id-1', + name: 'Awesome rule', + }, + }, + }, + ...overrides, }); export const getHostIsolationUserAction = ( @@ -860,9 +883,9 @@ export const basicCaseClosed: Case = { status: CaseStatuses.closed, }; -export const getExternalReferenceUserAction = (): SnakeToCamelCase< - UserActionWithResponse -> => ({ +export const getExternalReferenceUserAction = ( + overrides?: Record +): SnakeToCamelCase> => ({ ...getUserAction(ActionTypes.comment, Actions.create), actionId: 'external-reference-action-id', type: ActionTypes.comment, @@ -877,6 +900,7 @@ export const getExternalReferenceUserAction = (): SnakeToCamelCase< owner: SECURITY_SOLUTION_OWNER, }, }, + ...overrides, }); export const getExternalReferenceAttachment = ( @@ -892,9 +916,9 @@ export const getExternalReferenceAttachment = ( }), }); -export const getPersistableStateUserAction = (): SnakeToCamelCase< - UserActionWithResponse -> => ({ +export const getPersistableStateUserAction = ( + overrides?: Record +): SnakeToCamelCase> => ({ ...getUserAction(ActionTypes.comment, Actions.create), actionId: 'persistable-state-action-id', type: ActionTypes.comment, @@ -907,6 +931,7 @@ export const getPersistableStateUserAction = (): SnakeToCamelCase< owner: SECURITY_SOLUTION_OWNER, }, }, + ...overrides, }); export const getPersistableStateAttachment = ( diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts index 8764eb434213e..6e79b5e4ebf03 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/metadata_service/metadata_service.test.ts @@ -20,7 +20,7 @@ jest.mock('rxjs', () => { }); describe('MetadataService', () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); let metadataService: MetadataService; diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts index e6107874b6d5d..f086f20956778 100644 --- a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts @@ -31,47 +31,74 @@ describe('Cloud Links Plugin - public', () => { plugin.stop(); }); - test('calls maybeAddCloudLinks when cloud and security are enabled and it is an authenticated page', () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); - const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - const security = securityMock.createStart(); - plugin.start(coreStart, { cloud, security }); - expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(1); - }); + describe('Onboarding Setup Guide link registration', () => { + test('registers the Onboarding Setup Guide link when cloud is enabled and it is an authenticated page', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + plugin.start(coreStart, { cloud }); + expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).toHaveBeenCalledTimes(1); + }); - test('does not call maybeAddCloudLinks when security is disabled', () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); - const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - plugin.start(coreStart, { cloud }); - expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); - }); + test('does not register the Onboarding Setup Guide link when cloud is enabled but it is an unauthenticated page', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + plugin.start(coreStart, { cloud }); + expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled(); + }); - test('does not call maybeAddCloudLinks when the page is anonymous', () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); - const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; - const security = securityMock.createStart(); - plugin.start(coreStart, { cloud, security }); - expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + test('does not register the Onboarding Setup Guide link when cloud is not enabled', () => { + const coreStart = coreMock.createStart(); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: false }; + plugin.start(coreStart, { cloud }); + expect(coreStart.chrome.registerGlobalHelpExtensionMenuLink).not.toHaveBeenCalled(); + }); }); - test('does not call maybeAddCloudLinks when cloud is disabled', () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); - const security = securityMock.createStart(); - plugin.start(coreStart, { security }); - expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); - }); + describe('maybeAddCloudLinks', () => { + test('calls maybeAddCloudLinks when cloud and security are enabled and it is an authenticated page', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + const security = securityMock.createStart(); + plugin.start(coreStart, { cloud, security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(1); + }); + + test('does not call maybeAddCloudLinks when security is disabled', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + plugin.start(coreStart, { cloud }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); + + test('does not call maybeAddCloudLinks when the page is anonymous', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + const security = securityMock.createStart(); + plugin.start(coreStart, { cloud, security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); + + test('does not call maybeAddCloudLinks when cloud is disabled', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const security = securityMock.createStart(); + plugin.start(coreStart, { security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); - test('does not call maybeAddCloudLinks when isCloudEnabled is false', () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); - const cloud = { ...cloudMock.createStart(), isCloudEnabled: false }; - const security = securityMock.createStart(); - plugin.start(coreStart, { cloud, security }); - expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + test('does not call maybeAddCloudLinks when isCloudEnabled is false', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: false }; + const security = securityMock.createStart(); + plugin.start(coreStart, { cloud, security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); }); }); }); diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx similarity index 58% rename from x-pack/plugins/cloud_integrations/cloud_links/public/plugin.ts rename to x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx index 7fbf7a8a65064..0d64ed13cde9f 100755 --- a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.ts +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import type { CoreStart, Plugin } from '@kbn/core/public'; import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; @@ -26,12 +28,18 @@ export class CloudLinksPlugin public setup() {} public start(core: CoreStart, { cloud, security }: CloudLinksDepsStart) { - if ( - cloud?.isCloudEnabled && - security && - !core.http.anonymousPaths.isAnonymous(window.location.pathname) - ) { - maybeAddCloudLinks({ security, chrome: core.chrome, cloud }); + if (cloud?.isCloudEnabled && !core.http.anonymousPaths.isAnonymous(window.location.pathname)) { + core.chrome.registerGlobalHelpExtensionMenuLink({ + linkType: 'custom', + href: core.http.basePath.prepend('/app/home#/getting_started'), + content: , + 'data-test-subj': 'cloudOnboardingSetupGuideLink', + priority: 1000, // We want this link to be at the very top. + }); + + if (security) { + maybeAddCloudLinks({ security, chrome: core.chrome, cloud }); + } } } diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 764cb49dae8d2..a956800136483 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -49,3 +49,11 @@ export const CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE = 'csp-rule-template'; export const CLOUDBEAT_VANILLA = 'cloudbeat/cis_k8s'; // Integration input export const CLOUDBEAT_EKS = 'cloudbeat/cis_eks'; // Integration input + +export const LOCAL_STORAGE_PAGE_SIZE_LATEST_FINDINGS_KEY = 'cloudPosture:latestFindings:pageSize'; +export const LOCAL_STORAGE_PAGE_SIZE_RESOURCE_FINDINGS_KEY = + 'cloudPosture:resourceFindings:pageSize'; +export const LOCAL_STORAGE_PAGE_SIZE_FINDINGS_BY_RESOURCE_KEY = + 'cloudPosture:findingsByResource:pageSize'; +export const LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY = 'cloudPosture:benchmark:pageSize'; +export const LOCAL_STORAGE_PAGE_SIZE_RULES_KEY = 'cloudPosture:rules:pageSize'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx index fce512d95315b..ce615733e4b98 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.tsx @@ -6,6 +6,7 @@ */ import React, { useState } from 'react'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { EuiFieldSearch, EuiFieldSearchProps, @@ -30,6 +31,7 @@ import { } from './use_csp_benchmark_integrations'; import { extractErrorMessage } from '../../../common/utils/helpers'; import * as TEST_SUBJ from './test_subjects'; +import { LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY } from '../../../common/constants'; const SEARCH_DEBOUNCE_MS = 300; @@ -126,10 +128,14 @@ const BenchmarkSearchField = ({ }; export const Benchmarks = () => { + const [pageSize, setPageSize] = useLocalStorage( + LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY, + 10 + ); const [query, setQuery] = useState({ name: '', page: 1, - perPage: 10, + perPage: pageSize || 10, sortField: 'package_policy.name', sortOrder: 'asc', }); @@ -169,7 +175,7 @@ export const Benchmarks = () => { error={queryResult.error ? extractErrorMessage(queryResult.error) : undefined} loading={queryResult.isFetching} pageIndex={query.page} - pageSize={query.perPage} + pageSize={pageSize || query.perPage} sorting={{ // @ts-expect-error - EUI types currently do not support sorting by nested fields sort: { field: query.sortField, direction: query.sortOrder }, @@ -177,6 +183,7 @@ export const Benchmarks = () => { }} totalItemCount={totalItemCount} setQuery={({ page, sort }) => { + setPageSize(page.size); setQuery((current) => ({ ...current, page: page.index, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx index 3294ee8ec4965..92b6ca6dcc68e 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_container.tsx @@ -8,6 +8,7 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiBottomBar, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import type { Evaluation } from '../../../../common/types'; import { CloudPosturePageTitle } from '../../../components/cloud_posture_page_title'; import type { FindingsBaseProps } from '../types'; @@ -31,6 +32,7 @@ import { FindingsGroupBySelector } from '../layout/findings_group_by_selector'; import { useUrlQuery } from '../../../common/hooks/use_url_query'; import { ErrorCallout } from '../layout/error_callout'; import { getLimitProperties } from '../utils/get_limit_properties'; +import { LOCAL_STORAGE_PAGE_SIZE_LATEST_FINDINGS_KEY } from '../../../../common/constants'; export const getDefaultQuery = ({ query, @@ -48,7 +50,10 @@ const MAX_ITEMS = 500; export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery); const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery); - + const [pageSize, setPageSize] = useLocalStorage( + LOCAL_STORAGE_PAGE_SIZE_LATEST_FINDINGS_KEY, + urlQuery.pageSize + ); /** * Page URL query to ES query */ @@ -62,7 +67,10 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { * Page ES query result */ const findingsGroupByNone = useLatestFindings({ - ...getPaginationQuery({ pageIndex: urlQuery.pageIndex, pageSize: urlQuery.pageSize }), + ...getPaginationQuery({ + pageIndex: urlQuery.pageIndex, + pageSize: pageSize || urlQuery.pageSize, + }), query: baseEsQuery.query, sort: urlQuery.sort, enabled: !baseEsQuery.error, @@ -137,20 +145,21 @@ export const LatestFindingsContainer = ({ dataView }: FindingsBaseProps) => { loading={findingsGroupByNone.isFetching} items={findingsGroupByNone.data?.page || []} pagination={getPaginationTableParams({ - pageSize: urlQuery.pageSize, + pageSize: pageSize || urlQuery.pageSize, pageIndex: urlQuery.pageIndex, totalItemCount: limitedTotalItemCount, })} sorting={{ sort: { field: urlQuery.sort.field, direction: urlQuery.sort.direction }, }} - setTableOptions={({ page, sort }) => + setTableOptions={({ page, sort }) => { + setPageSize(page.size); setUrlQuery({ sort, pageIndex: page.index, pageSize: page.size, - }) - } + }); + }} onAddFilter={(field, value, negate) => setUrlQuery({ pageIndex: 0, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx index e9ce59fe9f0c0..982fd12fbc06f 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { Route, Switch } from 'react-router-dom'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import type { Evaluation } from '../../../../common/types'; import { CloudPosturePageTitle } from '../../../components/cloud_posture_page_title'; import { FindingsSearchBar } from '../layout/findings_search_bar'; @@ -30,6 +31,7 @@ import { findingsNavigation } from '../../../common/navigation/constants'; import { ResourceFindings } from './resource_findings/resource_findings_container'; import { ErrorCallout } from '../layout/error_callout'; import { FindingsDistributionBar } from '../layout/findings_distribution_bar'; +import { LOCAL_STORAGE_PAGE_SIZE_FINDINGS_BY_RESOURCE_KEY } from '../../../../common/constants'; const getDefaultQuery = ({ query, @@ -59,6 +61,10 @@ export const FindingsByResourceContainer = ({ dataView }: FindingsBaseProps) => const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery); const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery); + const [pageSize, setPageSize] = useLocalStorage( + LOCAL_STORAGE_PAGE_SIZE_FINDINGS_BY_RESOURCE_KEY, + urlQuery.pageSize + ); /** * Page URL query to ES query @@ -73,7 +79,10 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { * Page ES query result */ const findingsGroupByResource = useFindingsByResource({ - ...getPaginationQuery(urlQuery), + ...getPaginationQuery({ + pageIndex: urlQuery.pageIndex, + pageSize: pageSize || urlQuery.pageSize, + }), sortDirection: urlQuery.sortDirection, query: baseEsQuery.query, enabled: !baseEsQuery.error, @@ -148,17 +157,18 @@ const LatestFindingsByResource = ({ dataView }: FindingsBaseProps) => { loading={findingsGroupByResource.isFetching} items={findingsGroupByResource.data?.page || []} pagination={getPaginationTableParams({ - pageSize: urlQuery.pageSize, + pageSize: pageSize || urlQuery.pageSize, pageIndex: urlQuery.pageIndex, totalItemCount: findingsGroupByResource.data?.total || 0, })} - setTableOptions={({ sort, page }) => + setTableOptions={({ sort, page }) => { + setPageSize(page.size); setUrlQuery({ sortDirection: sort?.direction, pageIndex: page.index, pageSize: page.size, - }) - } + }); + }} sorting={{ sort: { field: 'failed_findings', direction: urlQuery.sortDirection }, }} diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx index 75997efaf6294..e61415b7d0af1 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx @@ -15,6 +15,7 @@ import { Link, useParams } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; import { generatePath } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { CspInlineDescriptionList } from '../../../../components/csp_inline_description_list'; import type { Evaluation } from '../../../../../common/types'; import { CspFinding } from '../../../../../common/schemas/csp_finding'; @@ -37,6 +38,7 @@ import { ResourceFindingsTable } from './resource_findings_table'; import { FindingsSearchBar } from '../../layout/findings_search_bar'; import { ErrorCallout } from '../../layout/error_callout'; import { FindingsDistributionBar } from '../../layout/findings_distribution_bar'; +import { LOCAL_STORAGE_PAGE_SIZE_RESOURCE_FINDINGS_KEY } from '../../../../../common/constants'; const getDefaultQuery = ({ query, @@ -90,6 +92,10 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { const params = useParams<{ resourceId: string }>(); const getPersistedDefaultQuery = usePersistedQuery(getDefaultQuery); const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery); + const [pageSize, setPageSize] = useLocalStorage( + LOCAL_STORAGE_PAGE_SIZE_RESOURCE_FINDINGS_KEY, + urlQuery.pageSize + ); /** * Page URL query to ES query @@ -105,7 +111,7 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { */ const resourceFindings = useResourceFindings({ ...getPaginationQuery({ - pageSize: urlQuery.pageSize, + pageSize: pageSize || urlQuery.pageSize, pageIndex: urlQuery.pageIndex, }), sort: urlQuery.sort, @@ -195,16 +201,17 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { loading={resourceFindings.isFetching} items={resourceFindings.data?.page || []} pagination={getPaginationTableParams({ - pageSize: urlQuery.pageSize, + pageSize: pageSize || urlQuery.pageSize, pageIndex: urlQuery.pageIndex, totalItemCount: resourceFindings.data?.total || 0, })} sorting={{ sort: { field: urlQuery.sort.field, direction: urlQuery.sort.direction }, }} - setTableOptions={({ page, sort }) => - setUrlQuery({ pageIndex: page.index, pageSize: page.size, sort }) - } + setTableOptions={({ page, sort }) => { + setPageSize(page.size); + setUrlQuery({ pageIndex: page.index, pageSize: page.size, sort }); + }} onAddFilter={(field, value, negate) => setUrlQuery({ pageIndex: 0, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx index cc5f10a0bf061..84f39bb150c26 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx @@ -7,6 +7,7 @@ import React, { useState, useMemo } from 'react'; import { EuiPanel, EuiSpacer } from '@elastic/eui'; import { useParams } from 'react-router-dom'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; import { extractErrorMessage, createCspRuleSearchFilterByPackagePolicy, @@ -22,6 +23,7 @@ import { } from './use_csp_rules'; import * as TEST_SUBJECTS from './test_subjects'; import { RuleFlyout } from './rules_flyout'; +import { LOCAL_STORAGE_PAGE_SIZE_RULES_KEY } from '../../../common/constants'; interface RulesPageData { rules_page: RuleSavedObject[]; @@ -68,6 +70,7 @@ export type PageUrlParams = Record<'policyId' | 'packagePolicyId', string>; export const RulesContainer = () => { const params = useParams(); const [selectedRuleId, setSelectedRuleId] = useState(null); + const [pageSize, setPageSize] = useLocalStorage(LOCAL_STORAGE_PAGE_SIZE_RULES_KEY, 10); const [rulesQuery, setRulesQuery] = useState({ filter: createCspRuleSearchFilterByPackagePolicy({ packagePolicyId: params.packagePolicyId, @@ -75,7 +78,7 @@ export const RulesContainer = () => { }), search: '', page: 0, - perPage: 10, + perPage: pageSize || 10, }); const { data, status, error } = useFindCspRules({ @@ -105,11 +108,12 @@ export const RulesContainer = () => { total={rulesPageData.total} error={rulesPageData.error} loading={rulesPageData.loading} - perPage={rulesQuery.perPage} + perPage={pageSize || rulesQuery.perPage} page={rulesQuery.page} - setPagination={(paginationQuery) => - setRulesQuery((currentQuery) => ({ ...currentQuery, ...paginationQuery })) - } + setPagination={(paginationQuery) => { + setPageSize(paginationQuery.perPage); + setRulesQuery((currentQuery) => ({ ...currentQuery, ...paginationQuery })); + }} setSelectedRuleId={setSelectedRuleId} selectedRuleId={selectedRuleId} /> diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js index 16d38d42d46b2..50e984eec194e 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js @@ -17,7 +17,7 @@ describe('', () => { let httpRequestsMockHelpers; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); ({ httpRequestsMockHelpers } = setupEnvironment()); }); diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx index 9962937fa80dc..771b27ace1c5b 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_type_icon/field_type_icon.test.tsx @@ -19,9 +19,10 @@ describe('FieldTypeIcon', () => { expect(typeIconComponent).toMatchSnapshot(); }); - test(`render with tooltip and test hovering`, () => { + // TODO: Broken with Jest 27 + test.skip(`render with tooltip and test hovering`, () => { // Use fake timers so we don't have to wait for the EuiToolTip timeout - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const typeIconComponent = mount( diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts index 7b281208f79e8..a0610dc546fbf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/add_analytics_collections/add_analytics_collection_logic.test.ts @@ -97,7 +97,7 @@ describe('addAnalyticsCollectionLogic', () => { describe('listeners', () => { describe('onApiSuccess', () => { it('should flash a success toast and navigate to collection view', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const { navigateToUrl } = mockKibanaValues; diff --git a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts index 08ca206671de0..baaea238fa6b4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/delete_analytics_collection_logic.test.ts @@ -57,7 +57,7 @@ describe('deleteAnalyticsCollectionLogic', () => { it('calls makeRequest on deleteAnalyticsCollections', async () => { const collectionName = 'name'; - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); DeleteAnalyticsCollectionLogic.actions.makeRequest = jest.fn(); DeleteAnalyticsCollectionLogic.actions.deleteAnalyticsCollection(collectionName); jest.advanceTimersByTime(150); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts index 614f3506b4ef9..17f3ab989162d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts @@ -119,7 +119,7 @@ describe('ApiLogsLogic', () => { describe('listeners', () => { describe('pollForApiLogs', () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const setIntervalSpy = jest.spyOn(global, 'setInterval'); it('starts a poll that calls fetchApiLogs at set intervals', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts index 59ec64c69d5a8..9064d1185a3d0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts @@ -73,7 +73,7 @@ describe('CrawlerLogic', () => { beforeEach(() => { jest.clearAllMocks(); - jest.useFakeTimers(); // this should be run before every test to reset these mocks + jest.useFakeTimers('legacy'); // this should be run before every test to reset these mocks mount(); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts index 4e25477d65dbf..4ea8dd55ffb44 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts @@ -380,7 +380,7 @@ describe('CurationLogic', () => { }); describe('updateCuration', () => { - beforeAll(() => jest.useFakeTimers()); + beforeAll(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); it('should make a PUT API call with queries and promoted/hidden IDs to update', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts index d82b4f5d4b055..1bb1a815d2364 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts @@ -255,7 +255,7 @@ describe('EngineLogic', () => { }); describe('pollEmptyEngine', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); afterEach(() => jest.clearAllTimers()); afterAll(() => jest.useRealTimers()); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts index fba8b118d4d24..a4ea5595bba3a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts @@ -329,7 +329,7 @@ describe('LogRetentionLogic', () => { }); describe('fetchLogRetention', () => { - beforeAll(() => jest.useFakeTimers()); + beforeAll(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); describe('isLogRetentionUpdating', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index bf0bad4f1088a..f413fefb7f89b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -341,7 +341,7 @@ describe('RelevanceTuningLogic', () => { describe('getSearchResults', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts index ee20fab3da66c..6712a32c94bcc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts @@ -83,7 +83,7 @@ describe('SampleResponseLogic', () => { describe('listeners', () => { describe('getSearchResults', () => { - beforeAll(() => jest.useFakeTimers()); + beforeAll(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); it('makes a search API request and calls getSearchResultsSuccess with the first result of the response', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts index a56d2f7d5c949..ab8426dce3636 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts @@ -78,7 +78,7 @@ describe('SearchLogic', () => { describe('listeners', () => { describe('search', () => { - beforeAll(() => jest.useFakeTimers()); + beforeAll(() => jest.useFakeTimers('legacy')); afterAll(() => jest.useRealTimers()); it('should make a GET API call with a search query', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/mappings/mappings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/mappings/mappings_logic.ts index e0e3db0a2d599..b68ae1f4e1775 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/mappings/mappings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/mappings/mappings_logic.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types'; +import type { IndicesGetMappingIndexMappingRecord } from '@elastic/elasticsearch/lib/api/types'; import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.test.ts index e4b817f3d5973..7487a7790f797 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/method_connector/add_connector_logic.test.ts @@ -53,7 +53,7 @@ describe('AddConnectorLogic', () => { describe('apiSuccess', () => { it('navigates to correct spot and flashes success toast', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); AddConnectorApiLogic.actions.apiSuccess({ indexName: 'success' } as any); await nextTick(); jest.advanceTimersByTime(1001); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts index f5ae61dc75dd9..8f22ba28b8dc3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/new_index/new_search_index_logic.test.ts @@ -86,7 +86,7 @@ describe('NewSearchIndexLogic', () => { }); }); it('calls makeRequest on whether API exists with a 150ms debounce', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); NewSearchIndexLogic.actions.makeRequest = jest.fn(); NewSearchIndexLogic.actions.setRawName('indexname'); await nextTick(); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/add_domain_form.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/add_domain_form.tsx index b0722ca50d2c7..e3d38256e9642 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/add_domain_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/add_domain_form.tsx @@ -19,6 +19,7 @@ import { EuiFieldText, EuiSpacer, EuiText, + EuiFormControlLayout, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -56,13 +57,14 @@ export const AddDomainForm: React.FC = () => { > - setAddDomainFormInputValue(e.target.value)} - fullWidth - /> + setAddDomainFormInputValue('') }}> + setAddDomainFormInputValue(e.target.value)} + fullWidth + /> + diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/validation_step_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/validation_step_panel.tsx index 07e86b5f92d9e..fb33f620f82a7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/validation_step_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/add_domain/validation_step_panel.tsx @@ -10,10 +10,12 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, + EuiLink, EuiMarkdownFormat, EuiPanel, EuiSpacer, EuiTitle, + getDefaultEuiMarkdownProcessingPlugins, } from '@elastic/eui'; import { CrawlerDomainValidationStep } from '../../../../../api/crawler/types'; @@ -27,6 +29,9 @@ interface ValidationStepPanelProps { step: CrawlerDomainValidationStep; } +const processingPlugins = getDefaultEuiMarkdownProcessingPlugins(); +processingPlugins[1][1].components.a = (props) => ; + export const ValidationStepPanel: React.FC = ({ step, label, @@ -49,7 +54,11 @@ export const ValidationStepPanel: React.FC = ({ {showErrorMessage && ( <> - + {step.message || ''} {action && ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.tsx index 44a22f258b506..c86fc2b3a20dc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { useActions, useValues } from 'kea'; -import { EuiBasicTableColumn, EuiBasicTable } from '@elastic/eui'; +import { EuiBasicTableColumn, EuiBasicTable, EuiButtonIcon, EuiCopy } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -39,18 +39,23 @@ export const DomainsTable: React.FC = () => { { field: 'url', name: i18n.translate('xpack.enterpriseSearch.crawler.domainsTable.column.domainURL', { - defaultMessage: 'Domain URL', + defaultMessage: 'Domain', }), render: (_, domain: CrawlerDomain) => ( - - {domain.url} - + <> + + {(copy) => } + + + {domain.url} + + ), }, { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents_logic.test.ts index b927e6a5738e7..1bd1e1a405ee3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/documents_logic.test.ts @@ -85,7 +85,7 @@ describe('DocumentsLogic', () => { describe('listeners', () => { describe('setSearchQuery', () => { it('make documents apiRequest request after 250ms debounce', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); DocumentsLogic.actions.makeRequest = jest.fn(); DocumentsLogic.actions.setSearchQuery('test'); await nextTick(); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts index e09b66051e4aa..1b0a11d17f546 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/index_view_logic.test.ts @@ -199,7 +199,7 @@ describe('IndexViewLogic', () => { describe('createNewFetchIndexTimeout', () => { it('should trigger fetchIndex after timeout', async () => { IndexViewLogic.actions.fetchIndex = jest.fn(); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); IndexViewLogic.actions.createNewFetchIndexTimeout(1); expect(IndexViewLogic.actions.fetchIndex).not.toHaveBeenCalled(); jest.advanceTimersByTime(2); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts index f0222becb7961..c605009d7eb0d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts @@ -163,4 +163,60 @@ describe('MlInferenceLogic', () => { }); }); }); + + describe('listeners', () => { + describe('createPipeline', () => { + const mockModelConfiguration = { + ...DEFAULT_VALUES.addInferencePipelineModal, + configuration: { + destinationField: '', + modelID: 'mock-model-id', + pipelineName: 'mock-pipeline-name', + sourceField: 'mock_text_field', + }, + indexName: 'my-index-123', + }; + it('calls makeCreatePipelineRequest when no destinationField is passed', () => { + mount({ + ...DEFAULT_VALUES, + addInferencePipelineModal: { + ...mockModelConfiguration, + }, + }); + jest.spyOn(MLInferenceLogic.actions, 'makeCreatePipelineRequest'); + MLInferenceLogic.actions.createPipeline(); + + expect(MLInferenceLogic.actions.makeCreatePipelineRequest).toHaveBeenCalledWith({ + destinationField: undefined, + indexName: mockModelConfiguration.indexName, + modelId: mockModelConfiguration.configuration.modelID, + pipelineName: mockModelConfiguration.configuration.pipelineName, + sourceField: mockModelConfiguration.configuration.sourceField, + }); + }); + + it('calls makeCreatePipelineRequest with passed destinationField', () => { + mount({ + ...DEFAULT_VALUES, + addInferencePipelineModal: { + ...mockModelConfiguration, + configuration: { + ...mockModelConfiguration.configuration, + destinationField: 'mockDestinationField', + }, + }, + }); + jest.spyOn(MLInferenceLogic.actions, 'makeCreatePipelineRequest'); + MLInferenceLogic.actions.createPipeline(); + + expect(MLInferenceLogic.actions.makeCreatePipelineRequest).toHaveBeenCalledWith({ + destinationField: 'mockDestinationField', + indexName: mockModelConfiguration.indexName, + modelId: mockModelConfiguration.configuration.modelID, + pipelineName: mockModelConfiguration.configuration.pipelineName, + sourceField: mockModelConfiguration.configuration.sourceField, + }); + }); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts index 5b9bd33643469..f4a968da1c2a1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts @@ -94,7 +94,6 @@ interface MLInferenceProcessorsActions { setAddInferencePipelineStep: (step: AddInferencePipelineSteps) => { step: AddInferencePipelineSteps; }; - setCreateErrors(errors: string[]): { errors: string[] }; setIndexName: (indexName: string) => { indexName: string }; setInferencePipelineConfiguration: (configuration: InferencePipelineConfiguration) => { configuration: InferencePipelineConfiguration; @@ -148,7 +147,6 @@ export const MLInferenceLogic = kea< clearFormErrors: true, createPipeline: true, setAddInferencePipelineStep: (step: AddInferencePipelineSteps) => ({ step }), - setCreateErrors: (errors: string[]) => ({ errors }), setFormErrors: (inputErrors: AddInferencePipelineFormErrors) => ({ inputErrors }), setIndexName: (indexName: string) => ({ indexName }), setInferencePipelineConfiguration: (configuration: InferencePipelineConfiguration) => ({ @@ -208,7 +206,6 @@ export const MLInferenceLogic = kea< sourceField: configuration.sourceField, }); }, - makeCreatePipelineRequest: () => actions.setCreateErrors([]), setIndexName: ({ indexName }) => { actions.makeMLModelsRequest(undefined); actions.makeMappingRequest({ indexName }); @@ -268,7 +265,7 @@ export const MLInferenceLogic = kea< [], { createApiError: (_, error) => getErrorsFromHttpResponse(error), - setCreateErrors: (_, { errors }) => errors, + makeCreatePipelineRequest: () => [], }, ], simulatePipelineErrors: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts index ff3b779d61e29..0ac8b0949690b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts @@ -7,7 +7,7 @@ import { LogicMounter, mockFlashMessageHelpers } from '../../../../__mocks__/kea_logic'; import { apiIndex, connectorIndex } from '../../../__mocks__/view_index.mock'; -import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; +import type { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; import { UpdatePipelineApiLogic } from '../../../api/connector/update_pipeline_api_logic'; import { FetchCustomPipelineApiLogic } from '../../../api/index/fetch_custom_pipeline_api_logic'; @@ -223,15 +223,15 @@ describe('PipelinesLogic', () => { expect(PipelinesLogic.values).toEqual({ ...DEFAULT_VALUES, + canSetPipeline: false, + canUseMlInferencePipeline: true, customPipelineData: indexPipelines, + hasIndexIngestionPipeline: true, index: { ...apiIndex, }, indexName, pipelineName: indexName, - canSetPipeline: false, - hasIndexIngestionPipeline: true, - canUseMlInferencePipeline: true, }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts index ff761b17e7388..d9b6a6248ce3b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/indices_logic.test.ts @@ -315,7 +315,7 @@ describe('IndicesLogic', () => { expect(IndicesLogic.actions.closeDeleteModal).toHaveBeenCalled(); }); it('calls makeRequest on fetchIndices', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); IndicesLogic.actions.makeRequest = jest.fn(); IndicesLogic.actions.fetchIndices({ meta: DEFAULT_META, returnHiddenIndices: false }); jest.advanceTimersByTime(150); @@ -326,7 +326,7 @@ describe('IndicesLogic', () => { }); }); it('calls makeRequest once on two fetchIndices calls within 150ms', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); IndicesLogic.actions.makeRequest = jest.fn(); IndicesLogic.actions.fetchIndices({ meta: DEFAULT_META, returnHiddenIndices: false }); jest.advanceTimersByTime(130); @@ -341,7 +341,7 @@ describe('IndicesLogic', () => { expect(IndicesLogic.actions.makeRequest).toHaveBeenCalledTimes(1); }); it('calls makeRequest twice on two fetchIndices calls outside 150ms', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); IndicesLogic.actions.makeRequest = jest.fn(); IndicesLogic.actions.fetchIndices({ meta: DEFAULT_META, returnHiddenIndices: false }); jest.advanceTimersByTime(150); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts index 75c9010ae9be5..a610cdc887d98 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.test.ts @@ -80,7 +80,7 @@ describe('SourcesLogic', () => { }; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); jest.clearAllMocks(); mount(); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts index bc82c95871676..a167c5a1c11c3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts @@ -239,7 +239,7 @@ describe('GroupsLogic', () => { }; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts index c9ccd1841cf76..df7fee347161d 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts @@ -223,7 +223,7 @@ describe('callEnterpriseSearchConfigAPI', () => { }); it('handles timeouts', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // Warning callEnterpriseSearchConfigAPI(mockDependencies); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.test.ts similarity index 97% rename from x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.test.ts index d9939afedaa5e..da72136402aca 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.test.ts @@ -7,7 +7,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; -import { getMlInferenceErrors } from './get_inference_errors'; +import { getMlInferenceErrors } from './get_ml_inference_errors'; describe('getMlInferenceErrors', () => { const indexName = 'my-index'; diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.ts similarity index 96% rename from x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.ts index 09adc656d576f..1556b478de21e 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_errors.ts @@ -12,7 +12,7 @@ import { import { ElasticsearchClient } from '@kbn/core/server'; -import { MlInferenceError } from '../../../common/types/pipelines'; +import { MlInferenceError } from '../../../../../common/types/pipelines'; export interface ErrorAggregationBucket extends AggregationsStringRareTermsBucketKeys { max_error_timestamp: { diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.test.ts similarity index 95% rename from x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.test.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.test.ts index 61d11b50ae489..461bd0e882614 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.test.ts @@ -11,9 +11,9 @@ import { } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { MlInferenceHistoryResponse } from '../../../common/types/pipelines'; +import { MlInferenceHistoryResponse } from '../../../../../common/types/pipelines'; -import { fetchMlInferencePipelineHistory } from './fetch_ml_inference_pipeline_history'; +import { fetchMlInferencePipelineHistory } from './get_ml_inference_pipeline_history'; const DEFAULT_RESPONSE: SearchResponse = { _shards: { diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.ts similarity index 94% rename from x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.ts index 70cbe687590ca..f6a01ec2b3e87 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_history.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history.ts @@ -12,7 +12,7 @@ import { import { ElasticsearchClient } from '@kbn/core/server'; -import { MlInferenceHistoryResponse } from '../../../common/types/pipelines'; +import { MlInferenceHistoryResponse } from '../../../../../common/types/pipelines'; export const fetchMlInferencePipelineHistory = async ( client: ElasticsearchClient, diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.test.ts similarity index 100% rename from x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.test.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.test.ts diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts similarity index 97% rename from x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts index 19654d0b2e936..6cb74d75dd6ce 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline.ts @@ -7,7 +7,7 @@ import { ElasticsearchClient } from '@kbn/core/server'; -import { DeleteMlInferencePipelineResponse } from '../../../../../common/types/pipelines'; +import { DeleteMlInferencePipelineResponse } from '../../../../../../common/types/pipelines'; import { detachMlInferencePipeline } from './detach_ml_inference_pipeline'; diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.test.ts similarity index 100% rename from x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.test.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.test.ts diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts similarity index 96% rename from x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts index 02d6c328a8e47..28f099fad6fa1 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline.ts @@ -8,9 +8,9 @@ import { IngestPutPipelineRequest } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; -import { DeleteMlInferencePipelineResponse } from '../../../../../common/types/pipelines'; +import { DeleteMlInferencePipelineResponse } from '../../../../../../common/types/pipelines'; -import { getInferencePipelineNameFromIndexName } from '../../../../utils/ml_inference_pipeline_utils'; +import { getInferencePipelineNameFromIndexName } from '../../../../../utils/ml_inference_pipeline_utils'; export const detachMlInferencePipeline = async ( indexName: string, diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts similarity index 99% rename from x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts index 941ef42aaa448..1a20551ab7911 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.test.ts @@ -9,7 +9,7 @@ import { errors } from '@elastic/elasticsearch'; import { ElasticsearchClient } from '@kbn/core/server'; import { MlTrainedModels } from '@kbn/ml-plugin/server'; -import { InferencePipeline, TrainedModelState } from '../../../common/types/pipelines'; +import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines'; import { fetchAndAddTrainedModelData, @@ -19,7 +19,7 @@ import { fetchMlInferencePipelineProcessors, fetchPipelineProcessorInferenceData, InferencePipelineData, -} from './fetch_ml_inference_pipeline_processors'; +} from './get_ml_inference_pipeline_processors'; const mockGetPipeline = { 'my-index@ml-inference': { diff --git a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.ts similarity index 96% rename from x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts rename to x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.ts index 1eabe28eb78b1..4a2ba80ca43ab 100644 --- a/x-pack/plugins/enterprise_search/server/lib/indices/fetch_ml_inference_pipeline_processors.ts +++ b/x-pack/plugins/enterprise_search/server/lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors.ts @@ -9,9 +9,9 @@ import { IngestGetPipelineResponse } from '@elastic/elasticsearch/lib/api/types' import { ElasticsearchClient } from '@kbn/core/server'; import { MlTrainedModels } from '@kbn/ml-plugin/server'; -import { getMlModelTypesForModelConfig } from '../../../common/ml_inference_pipeline'; -import { InferencePipeline, TrainedModelState } from '../../../common/types/pipelines'; -import { getInferencePipelineNameFromIndexName } from '../../utils/ml_inference_pipeline_utils'; +import { getMlModelTypesForModelConfig } from '../../../../../../common/ml_inference_pipeline'; +import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines'; +import { getInferencePipelineNameFromIndexName } from '../../../../../utils/ml_inference_pipeline_utils'; export type InferencePipelineData = InferencePipeline & { trainedModelName: string; @@ -202,7 +202,7 @@ export const fetchMlInferencePipelineProcessors = async ( indexName: string ): Promise => { if (!trainedModelsProvider) { - return Promise.reject(new Error('Machine Learning is not enabled')); + throw new Error('Machine Learning is not enabled'); } const allMlPipelines = await fetchMlInferencePipelines(client); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.test.ts similarity index 93% rename from x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.test.ts rename to x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.test.ts index 45953166667a5..0765fafcc9d1d 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.test.ts @@ -9,11 +9,7 @@ import { IngestProcessorContainer } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; import { MlTrainedModels } from '@kbn/ml-plugin/server'; -import { getMlInferencePipelines } from './get_inference_pipelines'; - -jest.mock('../indices/fetch_ml_inference_pipeline_processors', () => ({ - getMlModelConfigsForModelIds: jest.fn(), -})); +import { getMlInferencePipelines } from './get_ml_inference_pipelines'; describe('getMlInferencePipelines', () => { const mockClient = { @@ -109,10 +105,10 @@ describe('getMlInferencePipelines', () => { expect( (actualPipelines.pipeline1.processors as IngestProcessorContainer[])[1].inference?.model_id - ).toBeDefined(); + ).toEqual('model1'); expect( (actualPipelines.pipeline2.processors as IngestProcessorContainer[])[1].inference?.model_id - ).toBeDefined(); + ).toEqual('model2'); expect( (actualPipelines.pipeline3.processors as IngestProcessorContainer[])[1].inference?.model_id ).toEqual(''); // Redacted model ID @@ -121,7 +117,7 @@ describe('getMlInferencePipelines', () => { ).toEqual(''); expect( (actualPipelines.pipeline4.processors as IngestProcessorContainer[])[2].inference?.model_id - ).toBeDefined(); + ).toEqual('model2'); expect( (actualPipelines.pipeline4.processors as IngestProcessorContainer[])[3].inference?.model_id ).toEqual(''); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.ts similarity index 83% rename from x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.ts rename to x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.ts index 4bdf7e95d4a06..2dfc6951b2224 100644 --- a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_pipelines.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/ml_inference/get_ml_inference_pipelines.ts @@ -20,7 +20,7 @@ export const getMlInferencePipelines = async ( trainedModelsProvider: MlTrainedModels | undefined ): Promise> => { if (!trainedModelsProvider) { - return Promise.reject(new Error('Machine Learning is not enabled')); + throw new Error('Machine Learning is not enabled'); } // Fetch all ML inference pipelines and trained models that are accessible in the current @@ -37,17 +37,22 @@ export const getMlInferencePipelines = async ( // Process pipelines: check if the model_id is one of the redacted ones, if so, redact it in the // result as well - const inferencePipelinesResult: Record = {}; - Object.entries(fetchedInferencePipelines).forEach(([name, inferencePipeline]) => { - inferencePipelinesResult[name] = { - ...inferencePipeline, - processors: inferencePipeline.processors?.map((processor) => - redactModelIdIfInaccessible(processor, accessibleModelIds) - ), - }; - }); + const inferencePipelinesResult: Record = Object.entries( + fetchedInferencePipelines + ).reduce( + (currentPipelines, [name, inferencePipeline]) => ({ + ...currentPipelines, + [name]: { + ...inferencePipeline, + processors: inferencePipeline.processors?.map((processor) => + redactModelIdIfInaccessible(processor, accessibleModelIds) + ), + }, + }), + {} + ); - return Promise.resolve(inferencePipelinesResult); + return inferencePipelinesResult; }; /** diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts index 1ad901330caff..52039cc48173b 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts @@ -14,12 +14,15 @@ import { SharedServices } from '@kbn/ml-plugin/server/shared_services'; import { ErrorCode } from '../../../common/types/error_codes'; -jest.mock('../../lib/indices/fetch_ml_inference_pipeline_history', () => ({ +jest.mock('../../lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history', () => ({ fetchMlInferencePipelineHistory: jest.fn(), })); -jest.mock('../../lib/indices/fetch_ml_inference_pipeline_processors', () => ({ - fetchMlInferencePipelineProcessors: jest.fn(), -})); +jest.mock( + '../../lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors', + () => ({ + fetchMlInferencePipelineProcessors: jest.fn(), + }) +); jest.mock( '../../lib/indices/pipelines/ml_inference/pipeline_processors/create_ml_inference_pipeline', () => ({ @@ -33,13 +36,13 @@ jest.mock( }) ); jest.mock( - '../../lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline', + '../../lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline', () => ({ deleteMlInferencePipeline: jest.fn(), }) ); jest.mock( - '../../lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline', + '../../lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline', () => ({ detachMlInferencePipeline: jest.fn(), }) @@ -47,22 +50,22 @@ jest.mock( jest.mock('../../lib/indices/exists_index', () => ({ indexOrAliasExists: jest.fn(), })); -jest.mock('../../lib/ml_inference_pipeline/get_inference_errors', () => ({ +jest.mock('../../lib/indices/pipelines/ml_inference/get_ml_inference_errors', () => ({ getMlInferenceErrors: jest.fn(), })); -jest.mock('../../lib/ml_inference_pipeline/get_inference_pipelines', () => ({ +jest.mock('../../lib/pipelines/ml_inference/get_ml_inference_pipelines', () => ({ getMlInferencePipelines: jest.fn(), })); import { indexOrAliasExists } from '../../lib/indices/exists_index'; -import { fetchMlInferencePipelineHistory } from '../../lib/indices/fetch_ml_inference_pipeline_history'; -import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_inference_pipeline_processors'; +import { getMlInferenceErrors } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_errors'; +import { fetchMlInferencePipelineHistory } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history'; import { attachMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/attach_ml_pipeline'; import { createAndReferenceMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/create_ml_inference_pipeline'; -import { getMlInferenceErrors } from '../../lib/ml_inference_pipeline/get_inference_errors'; -import { getMlInferencePipelines } from '../../lib/ml_inference_pipeline/get_inference_pipelines'; -import { deleteMlInferencePipeline } from '../../lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline'; -import { detachMlInferencePipeline } from '../../lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline'; +import { deleteMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline'; +import { detachMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline'; +import { fetchMlInferencePipelineProcessors } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors'; +import { getMlInferencePipelines } from '../../lib/pipelines/ml_inference/get_ml_inference_pipelines'; import { ElasticsearchResponseError } from '../../utils/identify_exceptions'; import { registerIndexRoutes } from './indices'; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index ee497ba671faf..02a7dd528f872 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts @@ -31,18 +31,18 @@ import { createIndex } from '../../lib/indices/create_index'; import { indexOrAliasExists } from '../../lib/indices/exists_index'; import { fetchIndex } from '../../lib/indices/fetch_index'; import { fetchIndices } from '../../lib/indices/fetch_indices'; -import { fetchMlInferencePipelineHistory } from '../../lib/indices/fetch_ml_inference_pipeline_history'; -import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_inference_pipeline_processors'; import { generateApiKey } from '../../lib/indices/generate_api_key'; +import { getMlInferenceErrors } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_errors'; +import { fetchMlInferencePipelineHistory } from '../../lib/indices/pipelines/ml_inference/get_ml_inference_pipeline_history'; import { attachMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/attach_ml_pipeline'; import { createAndReferenceMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/create_ml_inference_pipeline'; -import { getMlInferenceErrors } from '../../lib/ml_inference_pipeline/get_inference_errors'; -import { getMlInferencePipelines } from '../../lib/ml_inference_pipeline/get_inference_pipelines'; +import { deleteMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline'; +import { detachMlInferencePipeline } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline'; +import { fetchMlInferencePipelineProcessors } from '../../lib/indices/pipelines/ml_inference/pipeline_processors/get_ml_inference_pipeline_processors'; import { createIndexPipelineDefinitions } from '../../lib/pipelines/create_pipeline_definitions'; import { getCustomPipelines } from '../../lib/pipelines/get_custom_pipelines'; import { getPipeline } from '../../lib/pipelines/get_pipeline'; -import { deleteMlInferencePipeline } from '../../lib/pipelines/ml_inference/pipeline_processors/delete_ml_inference_pipeline'; -import { detachMlInferencePipeline } from '../../lib/pipelines/ml_inference/pipeline_processors/detach_ml_inference_pipeline'; +import { getMlInferencePipelines } from '../../lib/pipelines/ml_inference/get_ml_inference_pipelines'; import { RouteDependencies } from '../../plugin'; import { createError } from '../../utils/create_error'; import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; diff --git a/x-pack/plugins/features/common/sub_feature.ts b/x-pack/plugins/features/common/sub_feature.ts index 2795e50bce473..58142fd88c0c3 100644 --- a/x-pack/plugins/features/common/sub_feature.ts +++ b/x-pack/plugins/features/common/sub_feature.ts @@ -16,6 +16,17 @@ export interface SubFeatureConfig { /** Display name for this sub-feature */ name: string; + /** + * Whether or not this privilege should only be granted to `All Spaces *`. Should be used for features that do not + * support Spaces. Defaults to `false`. + */ + requireAllSpaces?: boolean; + + /** + * Optional message to display on the Role Management screen when configuring permissions for this feature. + */ + privilegesTooltip?: string; + /** Collection of privilege groups */ privilegeGroups: readonly SubFeaturePrivilegeGroupConfig[]; } @@ -90,6 +101,10 @@ export class SubFeature { return this.config.privilegeGroups; } + public get requireAllSpaces() { + return this.config.requireAllSpaces ?? false; + } + public toRaw() { return { ...this.config }; } diff --git a/x-pack/plugins/features/server/feature_schema.ts b/x-pack/plugins/features/server/feature_schema.ts index 30b2ddea3d7d5..05d172887d870 100644 --- a/x-pack/plugins/features/server/feature_schema.ts +++ b/x-pack/plugins/features/server/feature_schema.ts @@ -163,6 +163,8 @@ const kibanaMutuallyExclusiveSubFeaturePrivilegeSchema = const kibanaSubFeatureSchema = schema.object({ name: schema.string(), + requireAllSpaces: schema.maybe(schema.boolean()), + privilegesTooltip: schema.maybe(schema.string()), privilegeGroups: schema.maybe( schema.arrayOf( schema.oneOf([ diff --git a/x-pack/plugins/files/common/constants.ts b/x-pack/plugins/files/common/constants.ts index be0bfa3ca80c4..665945c1c65c1 100644 --- a/x-pack/plugins/files/common/constants.ts +++ b/x-pack/plugins/files/common/constants.ts @@ -27,3 +27,5 @@ export const FILE_SHARE_SO_TYPE = 'fileShare'; * The name of the fixed size ES-backed blob store */ export const ES_FIXED_SIZE_INDEX_BLOB_STORE = 'esFixedSizeIndex' as const; + +export const FILES_MANAGE_PRIVILEGE = 'files:manageFiles' as const; diff --git a/x-pack/plugins/files/kibana.json b/x-pack/plugins/files/kibana.json index ad83b24ef0842..47637d78fa0de 100755 --- a/x-pack/plugins/files/kibana.json +++ b/x-pack/plugins/files/kibana.json @@ -9,7 +9,6 @@ "description": "File upload, download, sharing, and serving over HTTP implementation in Kibana.", "server": true, "ui": true, - "requiredPlugins": [], "requiredBundles": ["kibanaUtils"], "optionalPlugins": ["security", "usageCollection"] } diff --git a/x-pack/plugins/files/public/components/context.tsx b/x-pack/plugins/files/public/components/context.tsx index e55c0c45e4da6..a18ea212beffe 100644 --- a/x-pack/plugins/files/public/components/context.tsx +++ b/x-pack/plugins/files/public/components/context.tsx @@ -7,9 +7,14 @@ import React, { createContext, useContext, type FunctionComponent } from 'react'; import { FileKindsRegistry, getFileKindsRegistry } from '../../common/file_kinds_registry'; +import type { FilesClient } from '../types'; export interface FilesContextValue { registry: FileKindsRegistry; + /** + * A files client that will be used process uploads. + */ + client: FilesClient; } const FilesContextObject = createContext(null as unknown as FilesContextValue); @@ -21,10 +26,18 @@ export const useFilesContext = () => { } return ctx; }; -export const FilesContext: FunctionComponent = ({ children }) => { + +interface ContextProps { + /** + * A files client that will be used process uploads. + */ + client: FilesClient; +} +export const FilesContext: FunctionComponent = ({ client, children }) => { return ( diff --git a/x-pack/plugins/files/public/components/file_picker/components/clear_filter_button.tsx b/x-pack/plugins/files/public/components/file_picker/components/clear_filter_button.tsx new file mode 100644 index 0000000000000..14356b9b02bd4 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/components/clear_filter_button.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 React from 'react'; +import type { FunctionComponent } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { EuiLink } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { useFilePickerContext } from '../context'; + +import { i18nTexts } from '../i18n_texts'; + +interface Props { + onClick: () => void; +} + +export const ClearFilterButton: FunctionComponent = ({ onClick }) => { + const { state } = useFilePickerContext(); + const query = useObservable(state.queryDebounced$); + if (!query) { + return null; + } + return ( +
    + {i18nTexts.clearFilterButton} +
    + ); +}; diff --git a/x-pack/plugins/files/public/components/file_picker/components/error_content.tsx b/x-pack/plugins/files/public/components/file_picker/components/error_content.tsx new file mode 100644 index 0000000000000..c2925c793fe63 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/components/error_content.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 React from 'react'; +import type { FunctionComponent } from 'react'; +import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; +import { i18nTexts } from '../i18n_texts'; +import { useFilePickerContext } from '../context'; +import { useBehaviorSubject } from '../../use_behavior_subject'; + +interface Props { + error: Error; +} + +export const ErrorContent: FunctionComponent = ({ error }) => { + const { state } = useFilePickerContext(); + const isLoading = useBehaviorSubject(state.isLoading$); + return ( + {i18nTexts.loadingFilesErrorTitle}} + body={error.message} + actions={ + + {i18nTexts.retryButtonLabel} + + } + /> + ); +}; diff --git a/x-pack/plugins/files/public/components/file_picker/components/file_card.scss b/x-pack/plugins/files/public/components/file_picker/components/file_card.scss new file mode 100644 index 0000000000000..f2a10651f6dea --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/components/file_card.scss @@ -0,0 +1,5 @@ +.filesFilePicker { + .euiCard__content, .euiCard__description { + margin :0; // make the cards a little bit more compact + } +} \ No newline at end of file diff --git a/x-pack/plugins/files/public/components/file_picker/components/file_card.tsx b/x-pack/plugins/files/public/components/file_picker/components/file_card.tsx new file mode 100644 index 0000000000000..88c77f36a6c00 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/components/file_card.tsx @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { FunctionComponent } from 'react'; +import numeral from '@elastic/numeral'; +import useObservable from 'react-use/lib/useObservable'; +import { EuiCard, EuiText, EuiIcon, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { FileImageMetadata, FileJSON } from '../../../../common'; +import { Image } from '../../image'; +import { isImage } from '../../util'; +import { useFilePickerContext } from '../context'; + +import './file_card.scss'; + +interface Props { + file: FileJSON; +} + +export const FileCard: FunctionComponent = ({ file }) => { + const { kind, state, client } = useFilePickerContext(); + const { euiTheme } = useEuiTheme(); + const displayImage = isImage({ type: file.mimeType }); + + const isSelected = useObservable(state.watchFileSelected$(file.id), false); + + const imageHeight = `calc(${euiTheme.size.xxxl} * 2)`; + return ( + (isSelected ? state.unselectFile(file.id) : state.selectFile(file.id)), + }} + image={ +
    + {displayImage ? ( + {file.alt + ) : ( +
    + +
    + )} +
    + } + description={ + <> + + {file.name} + + + {numeral(file.size).format('0[.]0 b')} + {file.extension && ( + <> +   ·   + + {file.extension} + + + )} + + + } + hasBorder + /> + ); +}; diff --git a/x-pack/plugins/files/public/components/file_picker/components/file_grid.tsx b/x-pack/plugins/files/public/components/file_picker/components/file_grid.tsx new file mode 100644 index 0000000000000..2f2a9722d55b7 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/components/file_grid.tsx @@ -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 React from 'react'; +import type { FunctionComponent } from 'react'; +import { useEuiTheme, EuiEmptyPrompt } from '@elastic/eui'; +import { css } from '@emotion/react'; +import useObservable from 'react-use/lib/useObservable'; + +import { i18nTexts } from '../i18n_texts'; +import { useFilePickerContext } from '../context'; +import { FileCard } from './file_card'; + +export const FileGrid: FunctionComponent = () => { + const { state } = useFilePickerContext(); + const { euiTheme } = useEuiTheme(); + const files = useObservable(state.files$, []); + if (!files.length) { + return {i18nTexts.emptyFileGridPrompt}} titleSize="s" />; + } + return ( +
    + {files.map((file, idx) => ( + + ))} +
    + ); +}; diff --git a/x-pack/plugins/files/public/components/file_picker/components/modal_footer.tsx b/x-pack/plugins/files/public/components/file_picker/components/modal_footer.tsx new file mode 100644 index 0000000000000..d0d0e146d2c3b --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/components/modal_footer.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 { EuiFlexGroup, EuiModalFooter } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + +import { Pagination } from './pagination'; +import { SelectButton, Props as SelectButtonProps } from './select_button'; + +interface Props { + onDone: SelectButtonProps['onClick']; +} + +export const ModalFooter: FunctionComponent = ({ onDone }) => { + return ( + + + + + + + ); +}; diff --git a/x-pack/plugins/files/public/components/file_picker/components/pagination.tsx b/x-pack/plugins/files/public/components/file_picker/components/pagination.tsx new file mode 100644 index 0000000000000..bc2d0d444ba45 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/components/pagination.tsx @@ -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 React from 'react'; +import type { FunctionComponent } from 'react'; +import { EuiPagination } from '@elastic/eui'; +import { useFilePickerContext } from '../context'; +import { useBehaviorSubject } from '../../use_behavior_subject'; + +export const Pagination: FunctionComponent = () => { + const { state } = useFilePickerContext(); + const page = useBehaviorSubject(state.currentPage$); + const pageCount = useBehaviorSubject(state.totalPages$); + return ; +}; diff --git a/x-pack/plugins/files/public/components/file_picker/components/search_field.tsx b/x-pack/plugins/files/public/components/file_picker/components/search_field.tsx new file mode 100644 index 0000000000000..0235b03dd3fc1 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/components/search_field.tsx @@ -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 type { FunctionComponent } from 'react'; +import React from 'react'; +import { EuiFieldSearch } from '@elastic/eui'; +import { i18nTexts } from '../i18n_texts'; +import { useFilePickerContext } from '../context'; +import { useBehaviorSubject } from '../../use_behavior_subject'; + +export const SearchField: FunctionComponent = () => { + const { state } = useFilePickerContext(); + const query = useBehaviorSubject(state.query$); + const isLoading = useBehaviorSubject(state.isLoading$); + const hasFiles = useBehaviorSubject(state.hasFiles$); + return ( + state.setQuery(ev.target.value)} + /> + ); +}; diff --git a/x-pack/plugins/files/public/components/file_picker/components/select_button.tsx b/x-pack/plugins/files/public/components/file_picker/components/select_button.tsx new file mode 100644 index 0000000000000..ac5e241c01d53 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/components/select_button.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 { EuiButton } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; +import { useBehaviorSubject } from '../../use_behavior_subject'; +import { useFilePickerContext } from '../context'; +import { i18nTexts } from '../i18n_texts'; + +export interface Props { + onClick: (selectedFiles: string[]) => void; +} + +export const SelectButton: FunctionComponent = ({ onClick }) => { + const { state } = useFilePickerContext(); + const selectedFiles = useBehaviorSubject(state.selectedFileIds$); + return ( + onClick(selectedFiles)} + > + {selectedFiles.length > 1 + ? i18nTexts.selectFilesLabel(selectedFiles.length) + : i18nTexts.selectFileLabel} + + ); +}; diff --git a/x-pack/plugins/files/public/components/file_picker/components/title.tsx b/x-pack/plugins/files/public/components/file_picker/components/title.tsx new file mode 100644 index 0000000000000..de1015241f656 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/components/title.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import type { FunctionComponent } from 'react'; +import { EuiTitle } from '@elastic/eui'; +import { i18nTexts } from '../i18n_texts'; + +export const Title: FunctionComponent = () => ( + +

    {i18nTexts.title}

    +
    +); diff --git a/x-pack/plugins/files/public/components/file_picker/components/upload_files.tsx b/x-pack/plugins/files/public/components/file_picker/components/upload_files.tsx new file mode 100644 index 0000000000000..143d20fd63ec0 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/components/upload_files.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 from 'react'; +import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import { UploadFile } from '../../upload_file'; +import { useFilePickerContext } from '../context'; +import { i18nTexts } from '../i18n_texts'; + +interface Props { + kind: string; +} + +export const UploadFilesPrompt: FunctionComponent = ({ kind }) => { + const { state } = useFilePickerContext(); + return ( + {i18nTexts.emptyStatePrompt}} + body={ + +

    {i18nTexts.emptyStatePromptSubtitle}

    +
    + } + titleSize="s" + actions={[ + // TODO: We can remove this once the entire modal is an upload area + { + state.selectFile(file.map(({ id }) => id)); + state.retry(); + }} + />, + ]} + /> + ); +}; diff --git a/x-pack/plugins/files/public/components/file_picker/context.tsx b/x-pack/plugins/files/public/components/file_picker/context.tsx new file mode 100644 index 0000000000000..67e745b745829 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/context.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, { createContext, useContext, useMemo, useEffect } from 'react'; +import type { FunctionComponent } from 'react'; +import { useFilesContext, FilesContextValue } from '../context'; +import { FilePickerState, createFilePickerState } from './file_picker_state'; + +interface FilePickerContextValue extends FilesContextValue { + state: FilePickerState; + kind: string; +} + +const FilePickerCtx = createContext( + null as unknown as FilePickerContextValue +); + +interface FilePickerContextProps { + kind: string; + pageSize: number; +} +export const FilePickerContext: FunctionComponent = ({ + kind, + pageSize, + children, +}) => { + const filesContext = useFilesContext(); + const { client } = filesContext; + const state = useMemo( + () => createFilePickerState({ pageSize, client, kind }), + [pageSize, client, kind] + ); + useEffect(() => state.dispose, [state]); + return ( + + {children} + + ); +}; + +export const useFilePickerContext = (): FilePickerContextValue => { + const ctx = useContext(FilePickerCtx); + if (!ctx) throw new Error('FilePickerContext not found!'); + return ctx; +}; diff --git a/x-pack/plugins/files/public/components/file_picker/file_picker.scss b/x-pack/plugins/files/public/components/file_picker/file_picker.scss new file mode 100644 index 0000000000000..a7ec792564500 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/file_picker.scss @@ -0,0 +1,8 @@ +.filesFilePicker--fixed { + @include euiBreakpoint('m', 'l', 'xl', 'xxl') { + width: 75vw; + .euiModal__flex { + height: 75vw; + } + } +} \ No newline at end of file diff --git a/x-pack/plugins/files/public/components/file_picker/file_picker.stories.tsx b/x-pack/plugins/files/public/components/file_picker/file_picker.stories.tsx new file mode 100644 index 0000000000000..9d40b112b4060 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/file_picker.stories.tsx @@ -0,0 +1,198 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { ComponentMeta, ComponentStory } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import type { FileJSON } from '../../../common'; +import { FilesClient, FilesClientResponses } from '../../types'; +import { register } from '../stories_shared'; +import { base64dLogo } from '../image/image.constants.stories'; +import { FilesContext } from '../context'; +import { FilePicker, Props as FilePickerProps } from './file_picker'; + +const kind = 'filepicker'; +register({ + id: kind, + http: {}, + allowedMimeTypes: ['*'], +}); + +const defaultProps: FilePickerProps = { + kind, + onDone: action('done!'), + onClose: action('close!'), +}; + +export default { + title: 'components/FilePicker', + component: FilePicker, + args: defaultProps, + decorators: [ + (Story) => ( + Promise.reject(new Error('not so fast buster!')), + list: async (): Promise => ({ + files: [], + total: 0, + }), + } as unknown as FilesClient + } + > + + + ), + ], +} as ComponentMeta; + +const Template: ComponentStory = (props) => ; + +export const Empty = Template.bind({}); + +const d = new Date(); +let id = 0; +function createFileJSON(file?: Partial): FileJSON { + return { + alt: '', + created: d.toISOString(), + updated: d.toISOString(), + extension: 'png', + fileKind: kind, + id: String(++id), + meta: { + width: 1000, + height: 1000, + }, + mimeType: 'image/png', + name: 'my file', + size: 1, + status: 'READY', + ...file, + }; +} +export const BasicOne = Template.bind({}); +BasicOne.decorators = [ + (Story) => ( + `data:image/png;base64,${base64dLogo}`, + list: async (): Promise => ({ + files: [createFileJSON()], + total: 1, + }), + } as unknown as FilesClient + } + > + + + ), +]; + +export const BasicMany = Template.bind({}); +BasicMany.decorators = [ + (Story) => { + const files = [ + createFileJSON({ name: 'abc' }), + createFileJSON({ name: 'def' }), + createFileJSON({ name: 'efg' }), + createFileJSON({ name: 'foo' }), + createFileJSON({ name: 'bar' }), + createFileJSON(), + createFileJSON(), + ]; + + return ( + `data:image/png;base64,${base64dLogo}`, + list: async (): Promise => ({ + files, + total: files.length, + }), + } as unknown as FilesClient + } + > + + + ); + }, +]; + +export const BasicManyMany = Template.bind({}); +BasicManyMany.decorators = [ + (Story) => { + const array = new Array(102); + array.fill(null); + return ( + `data:image/png;base64,${base64dLogo}`, + list: async (): Promise => ({ + files: array.map((_, idx) => createFileJSON({ id: String(idx) })), + total: array.length, + }), + } as unknown as FilesClient + } + > + + + ); + }, +]; + +export const ErrorLoading = Template.bind({}); +ErrorLoading.decorators = [ + (Story) => { + const array = new Array(102); + array.fill(createFileJSON()); + return ( + `data:image/png;base64,${base64dLogo}`, + list: async () => { + throw new Error('stop'); + }, + } as unknown as FilesClient + } + > + + + ); + }, +]; + +export const TryFilter = Template.bind({}); +TryFilter.decorators = [ + (Story) => { + const array = { files: [createFileJSON()], total: 1 }; + return ( + <> +

    Try entering a filter!

    + `data:image/png;base64,${base64dLogo}`, + list: async ({ name }: { name: string[] }) => { + if (name) { + return { files: [], total: 0 }; + } + return array; + }, + } as unknown as FilesClient + } + > + + + + ); + }, +]; diff --git a/x-pack/plugins/files/public/components/file_picker/file_picker.test.tsx b/x-pack/plugins/files/public/components/file_picker/file_picker.test.tsx new file mode 100644 index 0000000000000..14b621050a0ef --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/file_picker.test.tsx @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { act } from 'react-dom/test-utils'; +import { registerTestBed } from '@kbn/test-jest-helpers'; + +import { createMockFilesClient } from '../../mocks'; +import { FilesContext } from '../context'; +import { FilePicker, Props } from './file_picker'; +import { + FileKindsRegistryImpl, + getFileKindsRegistry, + setFileKindsRegistry, +} from '../../../common/file_kinds_registry'; +import { FileJSON } from '../../../common'; + +describe('FilePicker', () => { + const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); + let client: ReturnType; + let onDone: jest.Mock; + let onClose: jest.Mock; + + async function initTestBed(props?: Partial) { + const createTestBed = registerTestBed((p: Props) => ( + + + + )); + + const testBed = await createTestBed({ + client, + kind: 'test', + onClose, + onDone, + ...props, + } as Props); + + const baseTestSubj = `filePickerModal`; + + const testSubjects = { + base: baseTestSubj, + searchField: `${baseTestSubj}.searchField`, + emptyPrompt: `${baseTestSubj}.emptyPrompt`, + errorPrompt: `${baseTestSubj}.errorPrompt`, + selectButton: `${baseTestSubj}.selectButton`, + loadingSpinner: `${baseTestSubj}.loadingSpinner`, + fileGrid: `${baseTestSubj}.fileGrid`, + }; + + return { + ...testBed, + actions: { + select: (n: number) => + act(() => { + const file = testBed.find(testSubjects.fileGrid).childAt(n).find(EuiButtonEmpty); + file.simulate('click'); + testBed.component.update(); + }), + done: () => + act(() => { + testBed.find(testSubjects.selectButton).simulate('click'); + }), + waitUntilLoaded: async () => { + let tries = 5; + while (tries) { + await act(async () => { + await sleep(100); + testBed.component.update(); + }); + if (!testBed.exists(testSubjects.loadingSpinner)) { + break; + } + --tries; + } + }, + }, + testSubjects, + }; + } + + beforeAll(() => { + setFileKindsRegistry(new FileKindsRegistryImpl()); + getFileKindsRegistry().register({ + id: 'test', + maxSizeBytes: 10000, + http: {}, + }); + }); + + beforeEach(() => { + jest.resetAllMocks(); + client = createMockFilesClient(); + onDone = jest.fn(); + onClose = jest.fn(); + }); + + it('intially shows a loadings spinner, then content', async () => { + client.list.mockImplementation(() => Promise.resolve({ files: [], total: 0 })); + const { exists, testSubjects, actions } = await initTestBed(); + expect(exists(testSubjects.loadingSpinner)).toBe(true); + await actions.waitUntilLoaded(); + expect(exists(testSubjects.loadingSpinner)).toBe(false); + }); + it('shows empty prompt when there are no files', async () => { + client.list.mockImplementation(() => Promise.resolve({ files: [], total: 0 })); + const { exists, testSubjects, actions } = await initTestBed(); + await actions.waitUntilLoaded(); + expect(exists(testSubjects.emptyPrompt)).toBe(true); + }); + it('returns the IDs of the selected files', async () => { + client.list.mockImplementation(() => + Promise.resolve({ files: [{ id: 'a' }, { id: 'b' }] as FileJSON[], total: 2 }) + ); + const { find, testSubjects, actions } = await initTestBed(); + await actions.waitUntilLoaded(); + expect(find(testSubjects.selectButton).props().disabled).toBe(true); + actions.select(0); + actions.select(1); + expect(find(testSubjects.selectButton).props().disabled).toBe(false); + actions.done(); + expect(onDone).toHaveBeenCalledTimes(1); + expect(onDone).toHaveBeenNthCalledWith(1, ['a', 'b']); + }); +}); diff --git a/x-pack/plugins/files/public/components/file_picker/file_picker.tsx b/x-pack/plugins/files/public/components/file_picker/file_picker.tsx new file mode 100644 index 0000000000000..72920b72a865d --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/file_picker.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 React from 'react'; +import type { FunctionComponent } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { + EuiModal, + EuiModalBody, + EuiModalHeader, + EuiLoadingSpinner, + EuiSpacer, + EuiFlexGroup, +} from '@elastic/eui'; + +import { useBehaviorSubject } from '../use_behavior_subject'; +import { useFilePickerContext, FilePickerContext } from './context'; + +import { Title } from './components/title'; +import { ErrorContent } from './components/error_content'; +import { UploadFilesPrompt } from './components/upload_files'; +import { FileGrid } from './components/file_grid'; +import { SearchField } from './components/search_field'; +import { ModalFooter } from './components/modal_footer'; + +import './file_picker.scss'; +import { ClearFilterButton } from './components/clear_filter_button'; + +export interface Props { + /** + * The file kind that was passed to the registry. + */ + kind: Kind; + /** + * Will be called when the modal is closed + */ + onClose: () => void; + /** + * Will be called after a user has a selected a set of files + */ + onDone: (fileIds: string[]) => void; + /** + * The number of results to show per page. + */ + pageSize?: number; +} + +const Component: FunctionComponent = ({ onClose, onDone }) => { + const { state, kind } = useFilePickerContext(); + + const hasFiles = useBehaviorSubject(state.hasFiles$); + const hasQuery = useBehaviorSubject(state.hasQuery$); + const isLoading = useBehaviorSubject(state.isLoading$); + const error = useBehaviorSubject(state.loadingError$); + + useObservable(state.files$); + + const renderFooter = () => ; + + return ( + + + + <SearchField /> + </EuiModalHeader> + {isLoading ? ( + <> + <EuiModalBody> + <EuiFlexGroup justifyContent="center" alignItems="center" gutterSize="none"> + <EuiLoadingSpinner data-test-subj="loadingSpinner" size="xl" /> + </EuiFlexGroup> + </EuiModalBody> + {renderFooter()} + </> + ) : Boolean(error) ? ( + <EuiModalBody> + <ErrorContent error={error as Error} /> + </EuiModalBody> + ) : !hasFiles && !hasQuery ? ( + <EuiModalBody> + <UploadFilesPrompt kind={kind} /> + </EuiModalBody> + ) : ( + <> + <EuiModalBody> + <FileGrid /> + <EuiSpacer /> + <ClearFilterButton onClick={() => state.setQuery(undefined)} /> + </EuiModalBody> + {renderFooter()} + </> + )} + </EuiModal> + ); +}; + +export const FilePicker: FunctionComponent<Props> = (props) => ( + <FilePickerContext pageSize={props.pageSize ?? 20} kind={props.kind}> + <Component {...props} /> + </FilePickerContext> +); + +/* eslint-disable import/no-default-export */ +export default FilePicker; diff --git a/x-pack/plugins/files/public/components/file_picker/file_picker_state.test.ts b/x-pack/plugins/files/public/components/file_picker/file_picker_state.test.ts new file mode 100644 index 0000000000000..79eb5cbfa529d --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/file_picker_state.test.ts @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +jest.mock('rxjs', () => { + const rxjs = jest.requireActual('rxjs'); + return { + ...rxjs, + debounceTime: rxjs.tap, + }; +}); + +import { TestScheduler } from 'rxjs/testing'; +import { merge, tap, of, NEVER } from 'rxjs'; +import { FileJSON } from '../../../common'; +import { FilePickerState, createFilePickerState } from './file_picker_state'; +import { createMockFilesClient } from '../../mocks'; + +const getTestScheduler = () => + new TestScheduler((actual, expected) => expect(actual).toEqual(expected)); + +describe('FilePickerState', () => { + let filePickerState: FilePickerState; + let filesClient: ReturnType<typeof createMockFilesClient>; + beforeEach(() => { + filesClient = createMockFilesClient(); + filePickerState = createFilePickerState({ + client: filesClient, + pageSize: 20, + kind: 'test', + }); + }); + it('starts off empty', () => { + expect(filePickerState.hasFilesSelected()).toBe(false); + }); + it('updates when files are added', () => { + getTestScheduler().run(({ expectObservable, cold, flush }) => { + const addFiles$ = cold('--a-b|').pipe(tap((id) => filePickerState.selectFile(id))); + expectObservable(addFiles$).toBe('--a-b|'); + expectObservable(filePickerState.selectedFileIds$).toBe('a-b-c-', { + a: [], + b: ['a'], + c: ['a', 'b'], + }); + flush(); + expect(filePickerState.hasFilesSelected()).toBe(true); + expect(filePickerState.getSelectedFileIds()).toEqual(['a', 'b']); + }); + }); + it('adds files simultaneously as one update', () => { + getTestScheduler().run(({ expectObservable, cold, flush }) => { + const addFiles$ = cold('--a|').pipe(tap(() => filePickerState.selectFile(['1', '2', '3']))); + expectObservable(addFiles$).toBe('--a|'); + expectObservable(filePickerState.selectedFileIds$).toBe('a-b-', { + a: [], + b: ['1', '2', '3'], + }); + flush(); + expect(filePickerState.hasFilesSelected()).toBe(true); + expect(filePickerState.getSelectedFileIds()).toEqual(['1', '2', '3']); + }); + }); + it('updates when files are removed', () => { + getTestScheduler().run(({ expectObservable, cold, flush }) => { + const addFiles$ = cold(' --a-b---c|').pipe(tap((id) => filePickerState.selectFile(id))); + const removeFiles$ = cold('------a|').pipe(tap((id) => filePickerState.unselectFile(id))); + expectObservable(merge(addFiles$, removeFiles$)).toBe('--a-b-a-c|'); + expectObservable(filePickerState.selectedFileIds$).toBe('a-b-c-d-e-', { + a: [], + b: ['a'], + c: ['a', 'b'], + d: ['b'], + e: ['b', 'c'], + }); + flush(); + expect(filePickerState.hasFilesSelected()).toBe(true); + expect(filePickerState.getSelectedFileIds()).toEqual(['b', 'c']); + }); + }); + it('does not add duplicates', () => { + getTestScheduler().run(({ expectObservable, cold, flush }) => { + const addFiles$ = cold('--a-b-a-a-a|').pipe(tap((id) => filePickerState.selectFile(id))); + expectObservable(addFiles$).toBe('--a-b-a-a-a|'); + expectObservable(filePickerState.selectedFileIds$).toBe('a-b-c-d-e-f-', { + a: [], + b: ['a'], + c: ['a', 'b'], + d: ['a', 'b'], + e: ['a', 'b'], + f: ['a', 'b'], + }); + flush(); + expect(filePickerState.hasFilesSelected()).toBe(true); + expect(filePickerState.getSelectedFileIds()).toEqual(['a', 'b']); + }); + }); + it('calls the API with the expected args', () => { + getTestScheduler().run(({ expectObservable, cold, flush }) => { + const files = [ + { id: 'a', name: 'a' }, + { id: 'b', name: 'b' }, + ] as FileJSON[]; + filesClient.list.mockImplementation(() => of({ files }) as any); + + const inputQuery = '-------a---b|'; + const inputPage = ' ---------------2|'; + + const query$ = cold(inputQuery).pipe(tap((q) => filePickerState.setQuery(q))); + expectObservable(query$).toBe(inputQuery); + + const page$ = cold(inputPage).pipe(tap((p) => filePickerState.setPage(+p))); + expectObservable(page$).toBe(inputPage); + + expectObservable(filePickerState.files$, '----^').toBe('----a--b---c---d', { + a: files, + b: files, + c: files, + d: files, + }); + + flush(); + expect(filesClient.list).toHaveBeenCalledTimes(4); + expect(filesClient.list).toHaveBeenNthCalledWith(1, { + abortSignal: expect.any(AbortSignal), + kind: 'test', + name: undefined, + page: 1, + perPage: 20, + status: ['READY'], + }); + expect(filesClient.list).toHaveBeenNthCalledWith(2, { + abortSignal: expect.any(AbortSignal), + kind: 'test', + name: ['*a*'], + page: 1, + perPage: 20, + status: ['READY'], + }); + expect(filesClient.list).toHaveBeenNthCalledWith(3, { + abortSignal: expect.any(AbortSignal), + kind: 'test', + name: ['*b*'], + page: 1, + perPage: 20, + status: ['READY'], + }); + expect(filesClient.list).toHaveBeenNthCalledWith(4, { + abortSignal: expect.any(AbortSignal), + kind: 'test', + name: ['*b*'], + page: 3, + perPage: 20, + status: ['READY'], + }); + }); + }); + it('cancels in flight requests', () => { + getTestScheduler().run(({ expectObservable, cold }) => { + filesClient.list.mockImplementationOnce(() => NEVER as any); + filesClient.list.mockImplementationOnce(() => of({ files: [], total: 0 }) as any); + const inputQuery = '------a|'; + const input$ = cold(inputQuery).pipe(tap((q) => filePickerState.setQuery(q))); + expectObservable(input$).toBe(inputQuery); + expectObservable(filePickerState.files$, '--^').toBe('------a-', { a: [] }); + expectObservable(filePickerState.loadingError$).toBe('a-b---c-', {}); + }); + }); +}); diff --git a/x-pack/plugins/files/public/components/file_picker/file_picker_state.ts b/x-pack/plugins/files/public/components/file_picker/file_picker_state.ts new file mode 100644 index 0000000000000..697f1fc58188d --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/file_picker_state.ts @@ -0,0 +1,208 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + map, + tap, + from, + switchMap, + Observable, + shareReplay, + debounceTime, + Subscription, + combineLatest, + BehaviorSubject, + distinctUntilChanged, +} from 'rxjs'; +import { FileJSON } from '../../../common'; +import { FilesClient } from '../../types'; + +function naivelyFuzzify(query: string): string { + return query.includes('*') ? query : `*${query}*`; +} + +export class FilePickerState { + /** + * Files the user has selected + */ + public readonly selectedFileIds$ = new BehaviorSubject<string[]>([]); + + public readonly isLoading$ = new BehaviorSubject<boolean>(true); + public readonly loadingError$ = new BehaviorSubject<undefined | Error>(undefined); + public readonly hasFiles$ = new BehaviorSubject<boolean>(false); + public readonly hasQuery$ = new BehaviorSubject<boolean>(false); + public readonly query$ = new BehaviorSubject<undefined | string>(undefined); + public readonly queryDebounced$ = this.query$.pipe(debounceTime(100)); + public readonly currentPage$ = new BehaviorSubject<number>(0); + public readonly totalPages$ = new BehaviorSubject<undefined | number>(undefined); + + /** + * This is how we keep a deduplicated list of file ids representing files a user + * has selected + */ + private readonly fileSet = new Set<string>(); + private readonly retry$ = new BehaviorSubject<void>(undefined); + private readonly subscriptions: Subscription[] = []; + private readonly internalIsLoading$ = new BehaviorSubject<boolean>(true); + + constructor( + private readonly client: FilesClient, + private readonly kind: string, + public readonly pageSize: number + ) { + this.subscriptions = [ + this.query$ + .pipe( + tap(() => this.setIsLoading(true)), + map((query) => Boolean(query)), + distinctUntilChanged() + ) + .subscribe(this.hasQuery$), + this.requests$.pipe(tap(() => this.setIsLoading(true))).subscribe(), + this.internalIsLoading$ + .pipe(debounceTime(100), distinctUntilChanged()) + .subscribe(this.isLoading$), + ]; + } + + private readonly requests$ = combineLatest([ + this.currentPage$.pipe(distinctUntilChanged()), + this.query$.pipe(distinctUntilChanged(), debounceTime(100)), + this.retry$, + ]); + + /** + * File objects we have loaded on the front end, stored here so that it can + * easily be passed to all relevant UI. + * + * @note This is not explicitly kept in sync with the selected files! + * @note This is not explicitly kept in sync with the selected files! + */ + public readonly files$ = this.requests$.pipe( + switchMap(([page, query]) => this.sendRequest(page, query)), + tap(({ total }) => this.updateTotalPages({ total })), + tap(({ total }) => this.hasFiles$.next(Boolean(total))), + map(({ files }) => files), + shareReplay() + ); + + private updateTotalPages = ({ total }: { total: number }): void => { + this.totalPages$.next(Math.ceil(total / this.pageSize)); + }; + + private sendNextSelectedFiles() { + this.selectedFileIds$.next(this.getSelectedFileIds()); + } + + private setIsLoading(value: boolean) { + this.internalIsLoading$.next(value); + } + + public selectFile = (fileId: string | string[]): void => { + (Array.isArray(fileId) ? fileId : [fileId]).forEach((id) => this.fileSet.add(id)); + this.sendNextSelectedFiles(); + }; + + private abort: undefined | (() => void) = undefined; + private sendRequest = ( + page: number, + query: undefined | string + ): Observable<{ files: FileJSON[]; total: number }> => { + if (this.abort) this.abort(); + this.setIsLoading(true); + this.loadingError$.next(undefined); + + const abortController = new AbortController(); + this.abort = () => { + try { + abortController.abort(); + } catch (e) { + // ignore + } + }; + + const request$ = from( + this.client.list({ + kind: this.kind, + name: query ? [naivelyFuzzify(query)] : undefined, + page: page + 1, + status: ['READY'], + perPage: this.pageSize, + abortSignal: abortController.signal, + }) + ).pipe( + tap(() => { + this.setIsLoading(false); + this.abort = undefined; + }), + shareReplay() + ); + + request$.subscribe({ + error: (e: Error) => { + if (e.name === 'AbortError') return; + this.setIsLoading(false); + this.loadingError$.next(e); + }, + }); + + return request$; + }; + + public retry = (): void => { + this.retry$.next(); + }; + + public hasFilesSelected = (): boolean => { + return this.fileSet.size > 0; + }; + + public unselectFile = (fileId: string): void => { + if (this.fileSet.delete(fileId)) this.sendNextSelectedFiles(); + }; + + public isFileIdSelected = (fileId: string): boolean => { + return this.fileSet.has(fileId); + }; + + public getSelectedFileIds = (): string[] => { + return Array.from(this.fileSet); + }; + + public setQuery = (query: undefined | string): void => { + if (query) this.query$.next(query); + else this.query$.next(undefined); + this.currentPage$.next(0); + }; + + public setPage = (page: number): void => { + this.currentPage$.next(page); + }; + + public dispose = (): void => { + for (const sub of this.subscriptions) sub.unsubscribe(); + }; + + watchFileSelected$ = (id: string): Observable<boolean> => { + return this.selectedFileIds$.pipe( + map(() => this.fileSet.has(id)), + distinctUntilChanged() + ); + }; +} + +interface CreateFilePickerArgs { + client: FilesClient; + kind: string; + pageSize: number; +} +export const createFilePickerState = ({ + pageSize, + client, + kind, +}: CreateFilePickerArgs): FilePickerState => { + return new FilePickerState(client, kind, pageSize); +}; diff --git a/x-pack/plugins/files/public/components/file_picker/i18n_texts.ts b/x-pack/plugins/files/public/components/file_picker/i18n_texts.ts new file mode 100644 index 0000000000000..2670ecd71b084 --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/i18n_texts.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const i18nTexts = { + title: i18n.translate('xpack.files.filePicker.title', { + defaultMessage: 'Select a file', + }), + loadingFilesErrorTitle: i18n.translate('xpack.files.filePicker.error.loadingTitle', { + defaultMessage: 'Could not load files', + }), + retryButtonLabel: i18n.translate('xpack.files.filePicker.error.retryButtonLabel', { + defaultMessage: 'Retry', + }), + emptyStatePrompt: i18n.translate('xpack.files.filePicker.emptyStatePrompt', { + defaultMessage: 'No files found', + }), + emptyStatePromptSubtitle: i18n.translate('xpack.files.filePicker.emptyStatePromptSubtitle', { + defaultMessage: 'Upload your first file.', + }), + selectFileLabel: i18n.translate('xpack.files.filePicker.selectFileButtonLable', { + defaultMessage: 'Select file', + }), + selectFilesLabel: (nrOfFiles: number) => + i18n.translate('xpack.files.filePicker.selectFilesButtonLable', { + defaultMessage: 'Select {nrOfFiles} files', + values: { nrOfFiles }, + }), + searchFieldPlaceholder: i18n.translate('xpack.files.filePicker.searchFieldPlaceholder', { + defaultMessage: 'my-file-*', + }), + emptyFileGridPrompt: i18n.translate('xpack.files.filePicker.emptyGridPrompt', { + defaultMessage: 'No files matched filter', + }), + loadMoreButtonLabel: i18n.translate('xpack.files.filePicker.loadMoreButtonLabel', { + defaultMessage: 'Load more', + }), + clearFilterButton: i18n.translate('xpack.files.filePicker.clearFilterButtonLabel', { + defaultMessage: 'Clear filter', + }), +}; diff --git a/x-pack/plugins/files/public/components/file_picker/index.tsx b/x-pack/plugins/files/public/components/file_picker/index.tsx new file mode 100644 index 0000000000000..47c892ef1cadd --- /dev/null +++ b/x-pack/plugins/files/public/components/file_picker/index.tsx @@ -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. + */ + +import React, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import type { Props } from './file_picker'; + +export type { Props as FilePickerProps }; + +const FilePickerContainer = lazy(() => import('./file_picker')); + +export const FilePicker = (props: Props) => ( + <Suspense fallback={<EuiLoadingSpinner size="xl" />}> + <FilePickerContainer {...props} /> + </Suspense> +); diff --git a/x-pack/plugins/files/public/components/image/components/img.tsx b/x-pack/plugins/files/public/components/image/components/img.tsx index 295b062ca1fd8..953eb93a19917 100644 --- a/x-pack/plugins/files/public/components/image/components/img.tsx +++ b/x-pack/plugins/files/public/components/image/components/img.tsx @@ -12,21 +12,25 @@ import { css } from '@emotion/react'; import { sizes } from '../styles'; export interface Props extends ImgHTMLAttributes<HTMLImageElement> { - hidden: boolean; size?: EuiImageSize; observerRef: (el: null | HTMLImageElement) => void; } export const Img = React.forwardRef<HTMLImageElement, Props>( - ({ observerRef, src, hidden, size, ...rest }, ref) => { + ({ observerRef, src, size, ...rest }, ref) => { const { euiTheme } = useEuiTheme(); const styles = [ css` transition: opacity ${euiTheme.animation.extraFast}; `, - hidden + !src ? css` visibility: hidden; + position: absolute; // ensure that empty img tag occupies full container + top: 0; + right: 0; + bottom: 0; + left: 0; ` : undefined, size ? sizes[size] : undefined, diff --git a/x-pack/plugins/files/public/components/image/image.stories.tsx b/x-pack/plugins/files/public/components/image/image.stories.tsx index 02daf7badb329..ff8825485f9cb 100644 --- a/x-pack/plugins/files/public/components/image/image.stories.tsx +++ b/x-pack/plugins/files/public/components/image/image.stories.tsx @@ -13,6 +13,7 @@ import { FilesContext } from '../context'; import { getImageMetadata } from '../util'; import { Image, Props } from './image'; import { getImageData as getBlob, base64dLogo } from './image.constants.stories'; +import { FilesClient } from '../../types'; const defaultArgs: Props = { alt: 'test', src: `data:image/png;base64,${base64dLogo}` }; @@ -22,7 +23,7 @@ export default { args: defaultArgs, decorators: [ (Story) => ( - <FilesContext> + <FilesContext client={{} as unknown as FilesClient}> <Story /> </FilesContext> ), diff --git a/x-pack/plugins/files/public/components/image/image.tsx b/x-pack/plugins/files/public/components/image/image.tsx index 915f45c828f66..e353fced3ec3e 100644 --- a/x-pack/plugins/files/public/components/image/image.tsx +++ b/x-pack/plugins/files/public/components/image/image.tsx @@ -54,12 +54,10 @@ export const Image = React.forwardRef<HTMLImageElement, Props>( const { isVisible, ref: observerRef } = useViewportObserver({ onFirstVisible }); useEffect(() => { - let unmounted = false; const id = window.setTimeout(() => { - if (!unmounted) setBlurDelayExpired(true); + setBlurDelayExpired(true); }, 200); return () => { - unmounted = true; window.clearTimeout(id); }; }, []); @@ -90,7 +88,6 @@ export const Image = React.forwardRef<HTMLImageElement, Props>( observerRef={observerRef} ref={ref} size={size} - hidden={!isVisible} src={isVisible ? src : undefined} alt={alt} onLoad={(ev) => { diff --git a/x-pack/plugins/files/public/components/image/viewport_observer.ts b/x-pack/plugins/files/public/components/image/viewport_observer.ts index a73e0f4067881..c0efe3d095594 100644 --- a/x-pack/plugins/files/public/components/image/viewport_observer.ts +++ b/x-pack/plugins/files/public/components/image/viewport_observer.ts @@ -25,7 +25,10 @@ export class ViewportObserver { opts: IntersectionObserverInit ) => IntersectionObserver ) { - this.intersectionObserver = getIntersectionObserver(this.handleChange, { root: null }); + this.intersectionObserver = getIntersectionObserver(this.handleChange, { + rootMargin: '0px', + root: null, + }); } /** diff --git a/x-pack/plugins/files/public/components/index.ts b/x-pack/plugins/files/public/components/index.ts index 533b37505b961..c5ab1382b4dfa 100644 --- a/x-pack/plugins/files/public/components/index.ts +++ b/x-pack/plugins/files/public/components/index.ts @@ -7,4 +7,5 @@ export { Image, type ImageProps } from './image'; export { UploadFile, type UploadFileProps } from './upload_file'; +export { FilePicker, type FilePickerProps } from './file_picker'; export { FilesContext } from './context'; diff --git a/x-pack/plugins/files/public/components/stories_shared.ts b/x-pack/plugins/files/public/components/stories_shared.ts new file mode 100644 index 0000000000000..a82ec3295b1d0 --- /dev/null +++ b/x-pack/plugins/files/public/components/stories_shared.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 { FileKind } from '../../common'; +import { + setFileKindsRegistry, + getFileKindsRegistry, + FileKindsRegistryImpl, +} from '../../common/file_kinds_registry'; + +setFileKindsRegistry(new FileKindsRegistryImpl()); +const fileKindsRegistry = getFileKindsRegistry(); +export const register: FileKindsRegistryImpl['register'] = (fileKind: FileKind) => { + if (!fileKindsRegistry.getAll().find((kind) => kind.id === fileKind.id)) { + getFileKindsRegistry().register(fileKind); + } +}; diff --git a/x-pack/plugins/files/public/components/upload_file/components/cancel_button.tsx b/x-pack/plugins/files/public/components/upload_file/components/cancel_button.tsx index 33a579ccc4c5f..b71a6221f3b2d 100644 --- a/x-pack/plugins/files/public/components/upload_file/components/cancel_button.tsx +++ b/x-pack/plugins/files/public/components/upload_file/components/cancel_button.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiButtonEmpty } from '@elastic/eui'; +import { EuiButton, EuiButtonIcon } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import React from 'react'; import { useBehaviorSubject } from '../../use_behavior_subject'; @@ -13,22 +13,33 @@ import { useUploadState } from '../context'; import { i18nTexts } from '../i18n_texts'; interface Props { + compressed?: boolean; onClick: () => void; } -export const CancelButton: FunctionComponent<Props> = ({ onClick }) => { +export const CancelButton: FunctionComponent<Props> = ({ onClick, compressed }) => { const uploadState = useUploadState(); const uploading = useBehaviorSubject(uploadState.uploading$); - return ( - <EuiButtonEmpty + const disabled = !uploading; + return compressed ? ( + <EuiButtonIcon + color="danger" + data-test-subj="cancelButtonIcon" + disabled={disabled} + iconType="cross" + aria-label={i18nTexts.cancel} + onClick={onClick} + /> + ) : ( + <EuiButton key="cancelButton" size="s" data-test-subj="cancelButton" - disabled={!uploading} + disabled={disabled} onClick={onClick} color="danger" > {i18nTexts.cancel} - </EuiButtonEmpty> + </EuiButton> ); }; diff --git a/x-pack/plugins/files/public/components/upload_file/components/control_button.tsx b/x-pack/plugins/files/public/components/upload_file/components/control_button.tsx index 6898adc9cce36..351cf2c39edd1 100644 --- a/x-pack/plugins/files/public/components/upload_file/components/control_button.tsx +++ b/x-pack/plugins/files/public/components/upload_file/components/control_button.tsx @@ -5,56 +5,36 @@ * 2.0. */ -import { EuiFlexGroup, EuiIcon, useEuiTheme } from '@elastic/eui'; -import { css } from '@emotion/react'; import type { FunctionComponent } from 'react'; import React from 'react'; import useObservable from 'react-use/lib/useObservable'; -import { euiThemeVars } from '@kbn/ui-theme'; import { useBehaviorSubject } from '../../use_behavior_subject'; import { useUploadState } from '../context'; -import { i18nTexts } from '../i18n_texts'; import { UploadButton } from './upload_button'; import { RetryButton } from './retry_button'; import { CancelButton } from './cancel_button'; -const { euiButtonHeightSmall } = euiThemeVars; - interface Props { onCancel: () => void; onUpload: () => void; immediate?: boolean; + compressed?: boolean; } -export const ControlButton: FunctionComponent<Props> = ({ onCancel, onUpload, immediate }) => { +export const ControlButton: FunctionComponent<Props> = ({ + onCancel, + onUpload, + immediate, + compressed, +}) => { const uploadState = useUploadState(); - const { - euiTheme: { size }, - } = useEuiTheme(); const uploading = useBehaviorSubject(uploadState.uploading$); const files = useObservable(uploadState.files$, []); - const done = useObservable(uploadState.done$); const retry = Boolean(files.some((f) => f.status === 'upload_failed')); - if (uploading) return <CancelButton onClick={onCancel} />; + if (compressed || uploading) return <CancelButton compressed={compressed} onClick={onCancel} />; if (retry) return <RetryButton onClick={onUpload} />; - if (!done && !immediate) return <UploadButton onClick={onUpload} />; + if (!immediate) return <UploadButton onClick={onUpload} />; - if (done) { - return ( - <EuiFlexGroup alignItems="center" gutterSize="none"> - <EuiIcon - css={css` - margin-inline: ${size.m}; - height: ${euiButtonHeightSmall}; - `} - data-test-subj="uploadSuccessIcon" - type="checkInCircleFilled" - color="success" - aria-label={i18nTexts.uploadDone} - /> - </EuiFlexGroup> - ); - } return null; }; diff --git a/x-pack/plugins/files/public/components/upload_file/components/retry_button.tsx b/x-pack/plugins/files/public/components/upload_file/components/retry_button.tsx index 55df91c5be3e8..355495aa25c17 100644 --- a/x-pack/plugins/files/public/components/upload_file/components/retry_button.tsx +++ b/x-pack/plugins/files/public/components/upload_file/components/retry_button.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiButtonEmpty } from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import React from 'react'; import { useBehaviorSubject } from '../../use_behavior_subject'; @@ -20,7 +20,7 @@ export const RetryButton: FunctionComponent<Props> = ({ onClick }) => { const uploading = useBehaviorSubject(uploadState.uploading$); return ( - <EuiButtonEmpty + <EuiButton key="retryButton" size="s" data-test-subj="retryButton" @@ -28,6 +28,6 @@ export const RetryButton: FunctionComponent<Props> = ({ onClick }) => { onClick={onClick} > {i18nTexts.retry} - </EuiButtonEmpty> + </EuiButton> ); }; diff --git a/x-pack/plugins/files/public/components/upload_file/components/upload_button.tsx b/x-pack/plugins/files/public/components/upload_file/components/upload_button.tsx index c9918874f7895..ff6320ab0b95a 100644 --- a/x-pack/plugins/files/public/components/upload_file/components/upload_button.tsx +++ b/x-pack/plugins/files/public/components/upload_file/components/upload_button.tsx @@ -21,16 +21,20 @@ export const UploadButton: FunctionComponent<Props> = ({ onClick }) => { const uploadState = useUploadState(); const uploading = useBehaviorSubject(uploadState.uploading$); const error = useBehaviorSubject(uploadState.error$); + const done = useObservable(uploadState.done$); const files = useObservable(uploadState.files$, []); return ( <EuiButton key="uploadButton" - disabled={Boolean(!files.length || uploading || error)} + isLoading={uploading} + color={done ? 'success' : 'primary'} + iconType={done ? 'checkInCircleFilled' : undefined} + disabled={Boolean(!files.length || error || done)} onClick={onClick} size="s" data-test-subj="uploadButton" > - {uploading ? i18nTexts.uploading : i18nTexts.upload} + {done ? i18nTexts.uploadComplete : uploading ? i18nTexts.uploading : i18nTexts.upload} </EuiButton> ); }; diff --git a/x-pack/plugins/files/public/components/upload_file/i18n_texts.ts b/x-pack/plugins/files/public/components/upload_file/i18n_texts.ts index 2077d635d3050..b58616e3d86b0 100644 --- a/x-pack/plugins/files/public/components/upload_file/i18n_texts.ts +++ b/x-pack/plugins/files/public/components/upload_file/i18n_texts.ts @@ -17,6 +17,9 @@ export const i18nTexts = { uploading: i18n.translate('xpack.files.uploadFile.uploadingButtonLabel', { defaultMessage: 'Uploading', }), + uploadComplete: i18n.translate('xpack.files.uploadFile.uploadCompleteButtonLabel', { + defaultMessage: 'Upload complete', + }), retry: i18n.translate('xpack.files.uploadFile.retryButtonLabel', { defaultMessage: 'Retry', }), diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.component.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.component.tsx index f4f5986d2f00b..8c81ae3fdcb21 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_file.component.tsx +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.component.tsx @@ -12,6 +12,7 @@ import { EuiFlexItem, EuiFlexGroup, EuiFilePicker, + useEuiTheme, useGeneratedHtmlId, } from '@elastic/eui'; import { euiThemeVars } from '@kbn/ui-theme'; @@ -25,15 +26,36 @@ import { useUploadState } from './context'; export interface Props { meta?: unknown; accept?: string; + multiple?: boolean; + fullWidth?: boolean; immediate?: boolean; allowClear?: boolean; + compressed?: boolean; initialFilePromptText?: string; } const { euiFormMaxWidth, euiButtonHeightSmall } = euiThemeVars; +const horizontalContainer = css` + display: flex; + flex-direction: row; +`; + export const UploadFile = React.forwardRef<EuiFilePicker, Props>( - ({ meta, accept, immediate, allowClear = false, initialFilePromptText }, ref) => { + ( + { + compressed, + meta, + accept, + immediate, + allowClear = false, + multiple, + initialFilePromptText, + fullWidth, + }, + ref + ) => { + const { euiTheme } = useEuiTheme(); const uploadState = useUploadState(); const uploading = useBehaviorSubject(uploadState.uploading$); const error = useBehaviorSubject(uploadState.error$); @@ -47,43 +69,59 @@ export const UploadFile = React.forwardRef<EuiFilePicker, Props>( return ( <div data-test-subj="filesUploadFile" - css={css` - max-width: ${euiFormMaxWidth}; - `} + css={[ + css` + max-width: ${fullWidth ? '100%' : euiFormMaxWidth}; + `, + compressed ? horizontalContainer : undefined, + ]} > <EuiFilePicker + fullWidth={fullWidth} aria-label={i18nTexts.defaultPickerLabel} id={id} ref={ref} onChange={(fs) => { uploadState.setFiles(Array.from(fs ?? [])); - if (immediate) uploadState.upload(meta); + if (immediate && uploadState.hasFiles()) uploadState.upload(meta); }} - multiple={false} + multiple={multiple} initialPromptText={initialFilePromptText} isLoading={uploading} isInvalid={isInvalid} accept={accept} disabled={Boolean(done?.length || uploading)} aria-describedby={errorMessage ? errorId : undefined} + display={compressed ? 'default' : 'large'} /> - <EuiSpacer size="s" /> + <EuiSpacer + size="s" + css={ + compressed + ? css` + width: ${euiTheme.size.s}; + ` + : undefined + } + /> <EuiFlexGroup justifyContent="flexStart" - alignItems="flexStart" - direction="rowReverse" - gutterSize="m" + alignItems={compressed ? 'center' : 'flexStart'} + direction={compressed ? undefined : 'rowReverse'} + gutterSize={compressed ? 'none' : 'm'} + responsive={false} > <EuiFlexItem grow={false}> <ControlButton + compressed={compressed} immediate={immediate} onCancel={uploadState.abort} onUpload={() => uploadState.upload(meta)} /> </EuiFlexItem> - {Boolean(!done && !uploading && errorMessage) && ( + {!compressed && Boolean(!done && !uploading && errorMessage) && ( <EuiFlexItem> <EuiText data-test-subj="error" @@ -99,7 +137,7 @@ export const UploadFile = React.forwardRef<EuiFilePicker, Props>( </EuiText> </EuiFlexItem> )} - {done?.length && allowClear && ( + {!compressed && done?.length && allowClear && ( <> <EuiFlexItem /> {/* Occupy middle space */} <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.stories.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.stories.tsx index c5a64d6d91a52..635400223fc17 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_file.stories.tsx +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.stories.tsx @@ -5,14 +5,10 @@ * 2.0. */ import React from 'react'; -import { ComponentStory } from '@storybook/react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; import { action } from '@storybook/addon-actions'; -import { - FileKindsRegistryImpl, - setFileKindsRegistry, - getFileKindsRegistry, -} from '../../../common/file_kinds_registry'; +import { register } from '../stories_shared'; import { FilesClient } from '../../types'; import { FilesContext } from '../context'; import { UploadFile, Props } from './upload_file'; @@ -24,28 +20,36 @@ const defaultArgs: Props = { kind, onDone: action('onDone'), onError: action('onError'), - client: { - create: async () => ({ file: { id: 'test' } }), - upload: () => sleep(1000), - } as unknown as FilesClient, }; export default { title: 'stateful/UploadFile', component: UploadFile, args: defaultArgs, -}; - -setFileKindsRegistry(new FileKindsRegistryImpl()); - -getFileKindsRegistry().register({ + decorators: [ + (Story) => ( + <FilesContext + client={ + { + create: async () => ({ file: { id: 'test' } }), + upload: () => sleep(1000), + } as unknown as FilesClient + } + > + <Story /> + </FilesContext> + ), + ], +} as ComponentMeta<typeof UploadFile>; + +register({ id: kind, http: {}, allowedMimeTypes: ['*'], }); const miniFile = 'miniFile'; -getFileKindsRegistry().register({ +register({ id: miniFile, http: {}, maxSizeBytes: 1, @@ -53,17 +57,13 @@ getFileKindsRegistry().register({ }); const zipOnly = 'zipOnly'; -getFileKindsRegistry().register({ +register({ id: zipOnly, http: {}, allowedMimeTypes: ['application/zip'], }); -const Template: ComponentStory<typeof UploadFile> = (props: Props) => ( - <FilesContext> - <UploadFile {...props} /> - </FilesContext> -); +const Template: ComponentStory<typeof UploadFile> = (props: Props) => <UploadFile {...props} />; export const Basic = Template.bind({}); @@ -73,27 +73,43 @@ AllowRepeatedUploads.args = { }; export const LongErrorUX = Template.bind({}); -LongErrorUX.args = { - client: { - create: async () => ({ file: { id: 'test' } }), - upload: async () => { - await sleep(1000); - throw new Error('Something went wrong while uploading! '.repeat(10).trim()); - }, - delete: async () => {}, - } as unknown as FilesClient, -}; +LongErrorUX.decorators = [ + (Story) => ( + <FilesContext + client={ + { + create: async () => ({ file: { id: 'test' } }), + upload: async () => { + await sleep(1000); + throw new Error('Something went wrong while uploading! '.repeat(10).trim()); + }, + delete: async () => {}, + } as unknown as FilesClient + } + > + <Story /> + </FilesContext> + ), +]; export const Abort = Template.bind({}); -Abort.args = { - client: { - create: async () => ({ file: { id: 'test' } }), - upload: async () => { - await sleep(60000); - }, - delete: async () => {}, - } as unknown as FilesClient, -}; +Abort.decorators = [ + (Story) => ( + <FilesContext + client={ + { + create: async () => ({ file: { id: 'test' } }), + upload: async () => { + await sleep(60000); + }, + delete: async () => {}, + } as unknown as FilesClient + } + > + <Story /> + </FilesContext> + ), +]; export const MaxSize = Template.bind({}); MaxSize.args = { @@ -118,24 +134,72 @@ ImmediateUpload.args = { export const ImmediateUploadError = Template.bind({}); ImmediateUploadError.args = { immediate: true, - client: { - create: async () => ({ file: { id: 'test' } }), - upload: async () => { - await sleep(1000); - throw new Error('Something went wrong while uploading!'); - }, - delete: async () => {}, - } as unknown as FilesClient, }; +ImmediateUploadError.decorators = [ + (Story) => ( + <FilesContext + client={ + { + create: async () => ({ file: { id: 'test' } }), + upload: async () => { + await sleep(1000); + throw new Error('Something went wrong while uploading!'); + }, + delete: async () => {}, + } as unknown as FilesClient + } + > + <Story /> + </FilesContext> + ), +]; export const ImmediateUploadAbort = Template.bind({}); +ImmediateUploadAbort.decorators = [ + (Story) => ( + <FilesContext + client={ + { + create: async () => ({ file: { id: 'test' } }), + upload: async () => { + await sleep(60000); + }, + delete: async () => {}, + } as unknown as FilesClient + } + > + <Story /> + </FilesContext> + ), +]; ImmediateUploadAbort.args = { immediate: true, - client: { - create: async () => ({ file: { id: 'test' } }), - upload: async () => { - await sleep(60000); - }, - delete: async () => {}, - } as unknown as FilesClient, }; + +export const Compressed = Template.bind({}); +Compressed.args = { + compressed: true, +}; + +export const CompressedError = Template.bind({}); +CompressedError.args = { + compressed: true, +}; +CompressedError.decorators = [ + (Story) => ( + <FilesContext + client={ + { + create: async () => ({ file: { id: 'test' } }), + upload: async () => { + await sleep(1000); + throw new Error('Something went wrong while uploading! '.repeat(10).trim()); + }, + delete: async () => {}, + } as unknown as FilesClient + } + > + <Story /> + </FilesContext> + ), +]; diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.test.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.test.tsx index 1812f74e180e3..7682b085eb060 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_file.test.tsx +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.test.tsx @@ -30,7 +30,7 @@ describe('UploadFile', () => { async function initTestBed(props?: Partial<Props>) { const createTestBed = registerTestBed((p: Props) => ( - <FilesContext> + <FilesContext client={client}> <UploadFile {...p} /> </FilesContext> )); @@ -50,8 +50,8 @@ describe('UploadFile', () => { uploadButton: `${baseTestSubj}.uploadButton`, retryButton: `${baseTestSubj}.retryButton`, cancelButton: `${baseTestSubj}.cancelButton`, + cancelButtonIcon: `${baseTestSubj}.cancelButtonIcon`, errorMessage: `${baseTestSubj}.error`, - successIcon: `${baseTestSubj}.uploadSuccessIcon`, }; return { @@ -110,12 +110,12 @@ describe('UploadFile', () => { client.create.mockResolvedValue({ file: { id: 'test', size: 1 } as FileJSON }); client.upload.mockResolvedValue({ size: 1, ok: true }); - const { actions, exists, testSubjects } = await initTestBed(); + const { actions, find, exists, testSubjects } = await initTestBed(); await actions.addFiles([{ name: 'test', size: 1 } as File]); await actions.upload(); await sleep(1000); expect(exists(testSubjects.errorMessage)).toBe(false); - expect(exists(testSubjects.successIcon)).toBe(true); + expect(find(testSubjects.uploadButton).text()).toMatch(/upload complete/i); expect(onDone).toHaveBeenCalledTimes(1); }); @@ -200,4 +200,20 @@ describe('UploadFile', () => { expect(onDone).not.toHaveBeenCalled(); }); + + it('only shows the cancel control in compressed mode', async () => { + const { actions, testSubjects, exists } = await initTestBed({ compressed: true }); + const assertButtons = () => { + expect(exists(testSubjects.cancelButtonIcon)).toBe(true); + expect(exists(testSubjects.cancelButton)).toBe(false); + expect(exists(testSubjects.retryButton)).toBe(false); + expect(exists(testSubjects.uploadButton)).toBe(false); + }; + + assertButtons(); + await actions.addFiles([{ name: 'test', size: 1 } as File]); + assertButtons(); + await actions.wait(1000); + assertButtons(); + }); }); diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.tsx index e85460ca7c1e3..d0ca3577b27a0 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_file.tsx +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.tsx @@ -7,7 +7,6 @@ import { EuiFilePicker } from '@elastic/eui'; import React, { type FunctionComponent, useRef, useEffect, useMemo } from 'react'; -import { FilesClient } from '../../types'; import { useFilesContext } from '../context'; @@ -37,10 +36,6 @@ export interface Props<Kind extends string = string> { * A file kind that should be registered during plugin startup. See {@link FileServiceStart}. */ kind: Kind; - /** - * A files client that will be used process uploads. - */ - client: FilesClient<any>; /** * Allow users to clear a file after uploading. * @@ -56,6 +51,10 @@ export interface Props<Kind extends string = string> { * Metadata that you want to associate with any uploaded files */ meta?: Record<string, unknown>; + /** + * Whether to display the file picker with width 100%; + */ + fullWidth?: boolean; /** * Whether this component should display a "done" state after processing an * upload or return to the initial state to allow for another upload. @@ -63,6 +62,10 @@ export interface Props<Kind extends string = string> { * @default false */ allowRepeatedUploads?: boolean; + /** + * The initial text prompt + */ + initialPromptText?: string; /** * Called when the an upload process fully completes */ @@ -72,6 +75,22 @@ export interface Props<Kind extends string = string> { * Called when an error occurs during upload */ onError?: (e: Error) => void; + + /** + * Whether to display the component in it's compact form. + * + * @default false + * + * @note passing "true" here implies true for allowRepeatedUplods and immediate. + */ + compressed?: boolean; + + /** + * Allow upload more than one file at a time + * + * @default false + */ + multiple?: boolean; } /** @@ -82,25 +101,29 @@ export interface Props<Kind extends string = string> { */ export const UploadFile = <Kind extends string = string>({ meta, - client, onDone, onError, + fullWidth, allowClear, + compressed = false, kind: kindId, + multiple = false, + initialPromptText, immediate = false, allowRepeatedUploads = false, }: Props<Kind>): ReturnType<FunctionComponent> => { - const { registry } = useFilesContext(); + const { registry, client } = useFilesContext(); const ref = useRef<null | EuiFilePicker>(null); const fileKind = registry.get(kindId); + const repeatedUploads = compressed || allowRepeatedUploads; const uploadState = useMemo( () => createUploadState({ client, fileKind, - allowRepeatedUploads, + allowRepeatedUploads: repeatedUploads, }), - [client, allowRepeatedUploads, fileKind] + [client, repeatedUploads, fileKind] ); /** @@ -122,11 +145,15 @@ export const UploadFile = <Kind extends string = string>({ return ( <context.Provider value={uploadState}> <Component + compressed={compressed} ref={ref} accept={fileKind.allowedMimeTypes?.join(',')} meta={meta} - immediate={immediate} + immediate={compressed || immediate} allowClear={allowClear} + fullWidth={fullWidth} + initialFilePromptText={initialPromptText} + multiple={multiple} /> </context.Provider> ); diff --git a/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts b/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts index 3a4e19adf8114..a834103a2c9cb 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts +++ b/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts @@ -206,4 +206,14 @@ describe('UploadState', () => { expectObservable(uploadState.clear$, '^').toBe(' ---0-', [undefined]); }); }); + + it('correctly detects when files are ready for upload', () => { + const file1 = { name: 'test' } as File; + const file2 = { name: 'test 2.png' } as File; + expect(uploadState.hasFiles()).toBe(false); + uploadState.setFiles([file1, file2]); + expect(uploadState.hasFiles()).toBe(true); + uploadState.setFiles([]); + expect(uploadState.hasFiles()).toBe(false); + }); }); diff --git a/x-pack/plugins/files/public/components/upload_file/upload_state.ts b/x-pack/plugins/files/public/components/upload_file/upload_state.ts index dd03eb7aee56a..13c4cc020b05a 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_state.ts +++ b/x-pack/plugins/files/public/components/upload_file/upload_state.ts @@ -239,6 +239,10 @@ export class UploadState { public dispose = (): void => { for (const sub of this.subscriptions) sub.unsubscribe(); }; + + public hasFiles(): boolean { + return this.files$$.getValue().length > 0; + } } export const createUploadState = ({ diff --git a/x-pack/plugins/files/public/components/util/image_metadata.ts b/x-pack/plugins/files/public/components/util/image_metadata.ts index 9358dda9d05ad..9c6e74f4c0101 100644 --- a/x-pack/plugins/files/public/components/util/image_metadata.ts +++ b/x-pack/plugins/files/public/components/util/image_metadata.ts @@ -8,8 +8,8 @@ import * as bh from 'blurhash'; import type { FileImageMetadata } from '../../../common'; -export function isImage(file: Blob | File): boolean { - return file.type?.startsWith('image/'); +export function isImage(file: { type?: string }): boolean { + return Boolean(file.type?.startsWith('image/')); } export const boxDimensions = { diff --git a/x-pack/plugins/files/public/files_client/files_client.ts b/x-pack/plugins/files/public/files_client/files_client.ts index a17929a4a100b..f92b2c91d2796 100644 --- a/x-pack/plugins/files/public/files_client/files_client.ts +++ b/x-pack/plugins/files/public/files_client/files_client.ts @@ -102,11 +102,12 @@ export function createFilesClient({ getById: ({ kind, ...args }) => { return http.get(apiRoutes.getByIdRoute(scopedFileKind ?? kind, args.id)); }, - list: ({ kind, page, perPage, ...body } = { kind: '' }) => { + list: ({ kind, page, perPage, abortSignal, ...body } = { kind: '' }) => { return http.post(apiRoutes.getListRoute(scopedFileKind ?? kind), { headers: commonBodyHeaders, query: { page, perPage }, body: JSON.stringify(body), + signal: abortSignal, }); }, update: ({ kind, id, ...body }) => { diff --git a/x-pack/plugins/files/public/index.ts b/x-pack/plugins/files/public/index.ts index f08b073e7485b..a9bd88615ff12 100644 --- a/x-pack/plugins/files/public/index.ts +++ b/x-pack/plugins/files/public/index.ts @@ -19,6 +19,8 @@ export { type ImageProps, UploadFile, type UploadFileProps, + FilePicker, + type FilePickerProps, } from './components'; export function plugin() { diff --git a/x-pack/plugins/files/public/types.ts b/x-pack/plugins/files/public/types.ts index 1cc69ac4ed23e..fcc5c11b1ae45 100644 --- a/x-pack/plugins/files/public/types.ts +++ b/x-pack/plugins/files/public/types.ts @@ -25,7 +25,9 @@ import type { } from '../common/api_routes'; type UnscopedClientMethodFrom<E extends HttpApiInterfaceEntryDefinition> = ( - args: E['inputs']['body'] & E['inputs']['params'] & E['inputs']['query'] + args: E['inputs']['body'] & + E['inputs']['params'] & + E['inputs']['query'] & { abortSignal?: AbortSignal } ) => Promise<E['output']>; /** diff --git a/x-pack/plugins/files/server/feature.ts b/x-pack/plugins/files/server/feature.ts new file mode 100644 index 0000000000000..1685e95cc9fa6 --- /dev/null +++ b/x-pack/plugins/files/server/feature.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 { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; +import { KibanaFeatureConfig } from '@kbn/features-plugin/common'; +import { i18n } from '@kbn/i18n'; +import { PLUGIN_ID } from '../common'; +import { FILES_MANAGE_PRIVILEGE } from '../common/constants'; +import { hiddenTypes } from './saved_objects'; + +// TODO: This should be registered once we have a management section for files content +export const filesFeature: KibanaFeatureConfig = { + id: PLUGIN_ID, + name: i18n.translate('xpack.files.featureRegistry.filesFeatureName', { + defaultMessage: 'Files', + }), + minimumLicense: 'basic', + order: 10000, + category: DEFAULT_APP_CATEGORIES.management, + app: [PLUGIN_ID], + privilegesTooltip: i18n.translate('xpack.files.featureRegistry.filesPrivilegesTooltip', { + defaultMessage: 'Provide access to files across all apps', + }), + privileges: { + all: { + app: [PLUGIN_ID], + savedObject: { + all: hiddenTypes, + read: hiddenTypes, + }, + ui: [], + api: [FILES_MANAGE_PRIVILEGE], + }, + read: { + app: [PLUGIN_ID], + savedObject: { + all: hiddenTypes, + read: hiddenTypes, + }, + ui: [], + }, + }, +}; diff --git a/x-pack/plugins/files/server/mocks.ts b/x-pack/plugins/files/server/mocks.ts index 033e94c3bfd7f..de9a495818ff2 100644 --- a/x-pack/plugins/files/server/mocks.ts +++ b/x-pack/plugins/files/server/mocks.ts @@ -6,7 +6,9 @@ */ import { KibanaRequest } from '@kbn/core/server'; import { DeeplyMockedKeys } from '@kbn/utility-types-jest'; -import { FileServiceFactory, FileServiceStart } from '.'; +import * as stream from 'stream'; +import { File } from '../common'; +import { FileClient, FileServiceFactory, FileServiceStart } from '.'; export const createFileServiceMock = (): DeeplyMockedKeys<FileServiceStart> => ({ create: jest.fn(), @@ -26,3 +28,51 @@ export const createFileServiceFactoryMock = (): DeeplyMockedKeys<FileServiceFact asInternal: jest.fn(createFileServiceMock), asScoped: jest.fn((_: KibanaRequest) => createFileServiceMock()), }); + +export const createFileMock = (): DeeplyMockedKeys<File> => { + const fileMock: DeeplyMockedKeys<File> = { + id: '123', + data: { + id: '123', + created: '2022-10-10T14:57:30.682Z', + updated: '2022-10-19T14:43:20.112Z', + name: 'test.txt', + mimeType: 'text/plain', + size: 1234, + extension: '.txt', + meta: {}, + alt: undefined, + fileKind: 'none', + status: 'READY', + }, + update: jest.fn(), + uploadContent: jest.fn(), + downloadContent: jest.fn().mockResolvedValue(new stream.Readable()), + delete: jest.fn(), + share: jest.fn(), + listShares: jest.fn(), + unshare: jest.fn(), + toJSON: jest.fn(), + }; + + fileMock.update.mockResolvedValue(fileMock); + fileMock.uploadContent.mockResolvedValue(fileMock); + + return fileMock; +}; + +export const createFileClientMock = (): DeeplyMockedKeys<FileClient> => { + const fileMock = createFileMock(); + + return { + fileKind: 'none', + create: jest.fn().mockResolvedValue(fileMock), + get: jest.fn().mockResolvedValue(fileMock), + update: jest.fn(), + delete: jest.fn(), + find: jest.fn().mockResolvedValue({ files: [fileMock], total: 1 }), + share: jest.fn(), + unshare: jest.fn(), + listShares: jest.fn().mockResolvedValue({ shares: [] }), + }; +}; diff --git a/x-pack/plugins/files/server/routes/find.ts b/x-pack/plugins/files/server/routes/find.ts index 4f5a6da46b455..9ec5ab681cb66 100644 --- a/x-pack/plugins/files/server/routes/find.ts +++ b/x-pack/plugins/files/server/routes/find.ts @@ -7,6 +7,7 @@ import { schema } from '@kbn/config-schema'; import type { CreateHandler, FilesRouter } from './types'; import { FileJSON } from '../../common'; +import { FILES_MANAGE_PRIVILEGE } from '../../common/constants'; import { FILES_API_ROUTES, CreateRouteDefinition } from './api_routes'; const method = 'post' as const; @@ -63,16 +64,14 @@ const handler: CreateHandler<Endpoint> = async ({ files }, req, res) => { }); }; -// TODO: Find out whether we want to add stricter access controls to this route. -// Currently this is giving read-access to all files which bypasses the -// security we set up on a per route level for the "getById" and "list" endpoints. -// Alternatively, we can remove the access controls on the "file kind" endpoints -// or remove them entirely. export function register(router: FilesRouter) { router[method]( { path: FILES_API_ROUTES.find, validate: { ...rt }, + options: { + tags: [`access:${FILES_MANAGE_PRIVILEGE}`], + }, }, handler ); diff --git a/x-pack/plugins/files/server/routes/metrics.ts b/x-pack/plugins/files/server/routes/metrics.ts index eb1d0ae39b9a1..9ae898e17bb87 100644 --- a/x-pack/plugins/files/server/routes/metrics.ts +++ b/x-pack/plugins/files/server/routes/metrics.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { FILES_MANAGE_PRIVILEGE } from '../../common/constants'; import type { FilesRouter } from './types'; import { FilesMetrics } from '../../common'; @@ -27,6 +28,9 @@ export function register(router: FilesRouter) { { path: FILES_API_ROUTES.metrics, validate: {}, + options: { + tags: [`access:${FILES_MANAGE_PRIVILEGE}`], + }, }, handler ); diff --git a/x-pack/plugins/fleet/.storybook/smoke.test.tsx b/x-pack/plugins/fleet/.storybook/smoke.test.tsx index 1fca60a1af4a4..a3984a42a5ab2 100644 --- a/x-pack/plugins/fleet/.storybook/smoke.test.tsx +++ b/x-pack/plugins/fleet/.storybook/smoke.test.tsx @@ -11,14 +11,16 @@ import { act } from 'react-dom/test-utils'; import initStoryshots from '@storybook/addon-storyshots'; describe('Fleet Storybook Smoke', () => { - initStoryshots({ - configPath: __dirname, - framework: 'react', - test: async ({ story }) => { - const renderer = mount(createElement(story.render)); - // wait until the element will perform all renders and resolve all promises (lazy loading, especially) - await act(() => new Promise((resolve) => setTimeout(resolve, 0))); - expect(renderer.html()).not.toContain('euiErrorBoundary'); - }, + test('Init', async () => { + await initStoryshots({ + configPath: __dirname, + framework: 'react', + test: async ({ story }) => { + const renderer = mount(createElement(story.render)); + // wait until the element will perform all renders and resolve all promises (lazy loading, especially) + await act(() => new Promise((resolve) => setTimeout(resolve, 0))); + expect(renderer.html()).not.toContain('euiErrorBoundary'); + }, + }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_add_agent_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_add_agent_modal.tsx index b6d568971140a..18be0df355395 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_add_agent_modal.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_add_agent_modal.tsx @@ -51,7 +51,7 @@ export const PostInstallAddAgentModal: React.FunctionComponent<{ <p> <FormattedMessage id="xpack.fleet.agentPolicy.postInstallAddAgentModalDescription" - defaultMessage="To complete this integration, add {elasticAgent} to your hosts to collect data and send it to Elastic Stack" + defaultMessage="To complete this integration, add {elasticAgent} to your hosts to collect data and send it to Elastic Stack." values={{ elasticAgent: <strong>Elastic Agent</strong>, }} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx index 98b1688308203..33503018b49a0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx @@ -110,7 +110,7 @@ describe('agent_list_page', () => { totalInactive: 0, }, }); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { diff --git a/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts b/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts index f05441f3db1ff..af6ae76a646b0 100644 --- a/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts +++ b/x-pack/plugins/fleet/server/services/elastic_agent_manifest.ts @@ -81,21 +81,12 @@ spec: - name: varlog mountPath: /var/log readOnly: true - - name: etc-kubernetes - mountPath: /hostfs/etc/kubernetes + - name: etc-full + mountPath: /hostfs/etc readOnly: true - name: var-lib mountPath: /hostfs/var/lib readOnly: true - - name: passwd - mountPath: /hostfs/etc/passwd - readOnly: true - - name: group - mountPath: /hostfs/etc/group - readOnly: true - - name: etcsysmd - mountPath: /hostfs/etc/systemd - readOnly: true volumes: - name: datastreams configMap: @@ -113,26 +104,15 @@ spec: - name: varlog hostPath: path: /var/log - # Needed for cloudbeat - - name: etc-kubernetes + # The following volumes are needed for Cloud Security Posture integration (cloudbeat) + # If you are not using this integration, then these volumes and the corresponding + # mounts can be removed. + - name: etc-full hostPath: - path: /etc/kubernetes - # Needed for cloudbeat + path: /etc - name: var-lib hostPath: path: /var/lib - # Needed for cloudbeat - - name: passwd - hostPath: - path: /etc/passwd - # Needed for cloudbeat - - name: group - hostPath: - path: /etc/group - # Needed for cloudbeat - - name: etcsysmd - hostPath: - path: /etc/systemd --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -367,21 +347,12 @@ spec: - name: varlog mountPath: /var/log readOnly: true - - name: etc-kubernetes - mountPath: /hostfs/etc/kubernetes + - name: etc-full + mountPath: /hostfs/etc readOnly: true - name: var-lib mountPath: /hostfs/var/lib readOnly: true - - name: passwd - mountPath: /hostfs/etc/passwd - readOnly: true - - name: group - mountPath: /hostfs/etc/group - readOnly: true - - name: etcsysmd - mountPath: /hostfs/etc/systemd - readOnly: true - name: etc-mid mountPath: /etc/machine-id readOnly: true @@ -398,26 +369,15 @@ spec: - name: varlog hostPath: path: /var/log - # Needed for cloudbeat - - name: etc-kubernetes + # The following volumes are needed for Cloud Security Posture integration (cloudbeat) + # If you are not using this integration, then these volumes and the corresponding + # mounts can be removed. + - name: etc-full hostPath: - path: /etc/kubernetes - # Needed for cloudbeat + path: /etc - name: var-lib hostPath: path: /var/lib - # Needed for cloudbeat - - name: passwd - hostPath: - path: /etc/passwd - # Needed for cloudbeat - - name: group - hostPath: - path: /etc/group - # Needed for cloudbeat - - name: etcsysmd - hostPath: - path: /etc/systemd # Mount /etc/machine-id from the host to determine host ID # Needed for Elastic Security integration - name: etc-mid diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts index 92ea60d290040..2cf665e0fc094 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/install.ts @@ -141,6 +141,7 @@ export async function installKibanaAssetsAndReferences({ pkgTitle, paths, installedPkg, + spaceId, }: { savedObjectsClient: SavedObjectsClientContract; savedObjectsImporter: Pick<ISavedObjectsImporter, 'import' | 'resolveImportErrors'>; @@ -151,6 +152,7 @@ export async function installKibanaAssetsAndReferences({ pkgTitle: string; paths: string[]; installedPkg?: SavedObject<Installation>; + spaceId: string; }) { const kibanaAssets = await getKibanaAssets(paths); if (installedPkg) await deleteKibanaSavedObjectsAssets({ savedObjectsClient, installedPkg }); @@ -167,7 +169,6 @@ export async function installKibanaAssetsAndReferences({ pkgName, kibanaAssets, }); - await withPackageSpan('Create and assign package tags', () => tagKibanaAssets({ savedObjectTagAssignmentService, @@ -175,6 +176,7 @@ export async function installKibanaAssetsAndReferences({ kibanaAssets, pkgTitle, pkgName, + spaceId, }) ); diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.test.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.test.ts index 3c946217d36b4..d887631240175 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.test.ts @@ -11,18 +11,18 @@ describe('tagKibanaAssets', () => { updateTagAssignments: jest.fn(), } as any; const savedObjectTagClient = { - getAll: jest.fn(), + get: jest.fn(), create: jest.fn(), } as any; beforeEach(() => { savedObjectTagAssignmentService.updateTagAssignments.mockReset(); - savedObjectTagClient.getAll.mockReset(); + savedObjectTagClient.get.mockReset(); savedObjectTagClient.create.mockReset(); }); - it('should create Managed and System tags when tagKibanaAssets with System package', async () => { - savedObjectTagClient.getAll.mockResolvedValue([]); + it('should create Managed and System tags when tagKibanaAssets with System package when no tags exist', async () => { + savedObjectTagClient.get.mockRejectedValue(new Error('not found')); savedObjectTagClient.create.mockImplementation(({ name }: { name: string }) => Promise.resolve({ id: name.toLowerCase(), name }) ); @@ -34,6 +34,7 @@ describe('tagKibanaAssets', () => { kibanaAssets, pkgTitle: 'System', pkgName: 'system', + spaceId: 'default', }); expect(savedObjectTagClient.create).toHaveBeenCalledWith( @@ -42,7 +43,7 @@ describe('tagKibanaAssets', () => { description: '', color: '#FFFFFF', }, - { id: 'managed', overwrite: true, refresh: false } + { id: 'fleet-managed-default', overwrite: true, refresh: false } ); expect(savedObjectTagClient.create).toHaveBeenCalledWith( { @@ -50,10 +51,10 @@ describe('tagKibanaAssets', () => { description: '', color: '#FFFFFF', }, - { id: 'system', overwrite: true, refresh: false } + { id: 'fleet-pkg-system-default', overwrite: true, refresh: false } ); expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledWith({ - tags: ['managed', 'system'], + tags: ['fleet-managed-default', 'fleet-pkg-system-default'], assign: kibanaAssets.dashboard, unassign: [], refresh: false, @@ -61,10 +62,7 @@ describe('tagKibanaAssets', () => { }); it('should only assign Managed and System tags when tags already exist', async () => { - savedObjectTagClient.getAll.mockResolvedValue([ - { id: 'managed', name: 'Managed' }, - { id: 'system', name: 'System' }, - ]); + savedObjectTagClient.get.mockResolvedValue({ name: '', color: '', description: '' }); const kibanaAssets = { dashboard: [{ id: 'dashboard1', type: 'dashboard' }] } as any; await tagKibanaAssets({ @@ -73,11 +71,12 @@ describe('tagKibanaAssets', () => { kibanaAssets, pkgTitle: 'System', pkgName: 'system', + spaceId: 'default', }); expect(savedObjectTagClient.create).not.toHaveBeenCalled(); expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledWith({ - tags: ['managed', 'system'], + tags: ['fleet-managed-default', 'fleet-pkg-system-default'], assign: kibanaAssets.dashboard, unassign: [], refresh: false, @@ -85,7 +84,7 @@ describe('tagKibanaAssets', () => { }); it('should skip non taggable asset types', async () => { - savedObjectTagClient.getAll.mockResolvedValue([]); + savedObjectTagClient.get.mockRejectedValue(new Error('tag not found')); savedObjectTagClient.create.mockImplementation(({ name }: { name: string }) => Promise.resolve({ id: name.toLowerCase(), name }) ); @@ -104,10 +103,11 @@ describe('tagKibanaAssets', () => { kibanaAssets, pkgTitle: 'System', pkgName: 'system', + spaceId: 'default', }); expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledWith({ - tags: ['managed', 'system'], + tags: ['fleet-managed-default', 'fleet-pkg-system-default'], assign: [ ...kibanaAssets.dashboard, ...kibanaAssets.search, @@ -129,8 +129,132 @@ describe('tagKibanaAssets', () => { kibanaAssets, pkgTitle: 'System', pkgName: 'system', + spaceId: 'default', }); expect(savedObjectTagAssignmentService.updateTagAssignments).not.toHaveBeenCalled(); }); + + it('should use legacy managed tag if it exists', async () => { + savedObjectTagClient.get.mockImplementation(async (id: string) => { + if (id === 'managed') return { name: 'managed', description: '', color: '' }; + + throw new Error('not found'); + }); + + savedObjectTagClient.create.mockImplementation(({ name }: { name: string }) => + Promise.resolve({ id: name.toLowerCase(), name }) + ); + const kibanaAssets = { dashboard: [{ id: 'dashboard1', type: 'dashboard' }] } as any; + + await tagKibanaAssets({ + savedObjectTagAssignmentService, + savedObjectTagClient, + kibanaAssets, + pkgTitle: 'System', + pkgName: 'system', + spaceId: 'default', + }); + + expect(savedObjectTagClient.create).not.toHaveBeenCalledWith( + { + name: 'Managed', + description: '', + color: '#FFFFFF', + }, + { id: 'fleet-managed-default', overwrite: true, refresh: false } + ); + + expect(savedObjectTagClient.create).toHaveBeenCalledWith( + { + name: 'System', + description: '', + color: '#FFFFFF', + }, + { id: 'fleet-pkg-system-default', overwrite: true, refresh: false } + ); + expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledWith({ + tags: ['managed', 'fleet-pkg-system-default'], + assign: kibanaAssets.dashboard, + unassign: [], + refresh: false, + }); + }); + + it('should use legacy package tag if it exists', async () => { + savedObjectTagClient.get.mockImplementation(async (id: string) => { + if (id === 'system') return { name: 'system', description: '', color: '' }; + + throw new Error('not found'); + }); + + savedObjectTagClient.create.mockImplementation(({ name }: { name: string }) => + Promise.resolve({ id: name.toLowerCase(), name }) + ); + const kibanaAssets = { dashboard: [{ id: 'dashboard1', type: 'dashboard' }] } as any; + + await tagKibanaAssets({ + savedObjectTagAssignmentService, + savedObjectTagClient, + kibanaAssets, + pkgTitle: 'System', + pkgName: 'system', + spaceId: 'default', + }); + + expect(savedObjectTagClient.create).toHaveBeenCalledWith( + { + name: 'Managed', + description: '', + color: '#FFFFFF', + }, + { id: 'fleet-managed-default', overwrite: true, refresh: false } + ); + + expect(savedObjectTagClient.create).not.toHaveBeenCalledWith( + { + name: 'System', + description: '', + color: '#FFFFFF', + }, + { id: 'system', overwrite: true, refresh: false } + ); + expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledWith({ + tags: ['fleet-managed-default', 'system'], + assign: kibanaAssets.dashboard, + unassign: [], + refresh: false, + }); + }); + + it('should use both legacy tags if they exist', async () => { + savedObjectTagClient.get.mockImplementation(async (id: string) => { + if (id === 'managed') return { name: 'managed', description: '', color: '' }; + if (id === 'system') return { name: 'system', description: '', color: '' }; + + throw new Error('not found'); + }); + + savedObjectTagClient.create.mockImplementation(({ name }: { name: string }) => + Promise.resolve({ id: name.toLowerCase(), name }) + ); + const kibanaAssets = { dashboard: [{ id: 'dashboard1', type: 'dashboard' }] } as any; + + await tagKibanaAssets({ + savedObjectTagAssignmentService, + savedObjectTagClient, + kibanaAssets, + pkgTitle: 'System', + pkgName: 'system', + spaceId: 'default', + }); + + expect(savedObjectTagClient.create).not.toHaveBeenCalled(); + expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledWith({ + tags: ['managed', 'system'], + assign: kibanaAssets.dashboard, + unassign: [], + refresh: false, + }); + }); }); diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.ts b/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.ts index 842932d71359e..1d61c3c908872 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/assets/tag_assets.ts @@ -15,22 +15,45 @@ import { KibanaSavedObjectTypeMapping } from './install'; const TAG_COLOR = '#FFFFFF'; const MANAGED_TAG_NAME = 'Managed'; -const MANAGED_TAG_ID = 'managed'; - -export async function tagKibanaAssets({ - savedObjectTagAssignmentService, - savedObjectTagClient, - kibanaAssets, - pkgTitle, - pkgName, -}: { +const LEGACY_MANAGED_TAG_ID = 'managed'; + +const getManagedTagId = (spaceId: string) => `fleet-managed-${spaceId}`; +const getPackageTagId = (spaceId: string, pkgName: string) => `fleet-pkg-${pkgName}-${spaceId}`; +const getLegacyPackageTagId = (pkgName: string) => pkgName; + +interface TagAssetsParams { savedObjectTagAssignmentService: IAssignmentService; savedObjectTagClient: ITagsClient; kibanaAssets: Record<KibanaAssetType, ArchiveAsset[]>; pkgTitle: string; pkgName: string; -}) { - const taggableAssets = Object.entries(kibanaAssets).flatMap(([assetType, assets]) => { + spaceId: string; +} + +export async function tagKibanaAssets(opts: TagAssetsParams) { + const { savedObjectTagAssignmentService, kibanaAssets } = opts; + const taggableAssets = getTaggableAssets(kibanaAssets); + + // no assets to tag + if (taggableAssets.length === 0) { + return; + } + + const [managedTagId, packageTagId] = await Promise.all([ + ensureManagedTag(opts), + ensurePackageTag(opts), + ]); + + await savedObjectTagAssignmentService.updateTagAssignments({ + tags: [managedTagId, packageTagId], + assign: taggableAssets, + unassign: [], + refresh: false, + }); +} + +function getTaggableAssets(kibanaAssets: TagAssetsParams['kibanaAssets']) { + return Object.entries(kibanaAssets).flatMap(([assetType, assets]) => { if (!taggableTypes.includes(KibanaSavedObjectTypeMapping[assetType as KibanaAssetType])) { return []; } @@ -41,41 +64,57 @@ export async function tagKibanaAssets({ return assets; }); +} - // no assets to tag - if (taggableAssets.length === 0) { - return; - } +async function ensureManagedTag( + opts: Pick<TagAssetsParams, 'spaceId' | 'savedObjectTagClient'> +): Promise<string> { + const { spaceId, savedObjectTagClient } = opts; - const allTags = await savedObjectTagClient.getAll(); - let managedTag = allTags.find((tag) => tag.name === MANAGED_TAG_NAME); - if (!managedTag) { - managedTag = await savedObjectTagClient.create( - { - name: MANAGED_TAG_NAME, - description: '', - color: TAG_COLOR, - }, - { id: MANAGED_TAG_ID, overwrite: true, refresh: false } - ); - } + const managedTagId = getManagedTagId(spaceId); + const managedTag = await savedObjectTagClient.get(managedTagId).catch(() => {}); - let packageTag = allTags.find((tag) => tag.name === pkgTitle); - if (!packageTag) { - packageTag = await savedObjectTagClient.create( - { - name: pkgTitle, - description: '', - color: TAG_COLOR, - }, - { id: pkgName, overwrite: true, refresh: false } - ); - } + if (managedTag) return managedTagId; - await savedObjectTagAssignmentService.updateTagAssignments({ - tags: [managedTag.id, packageTag.id], - assign: taggableAssets, - unassign: [], - refresh: false, - }); + const legacyManagedTag = await savedObjectTagClient.get(LEGACY_MANAGED_TAG_ID).catch(() => {}); + + if (legacyManagedTag) return LEGACY_MANAGED_TAG_ID; + + await savedObjectTagClient.create( + { + name: MANAGED_TAG_NAME, + description: '', + color: TAG_COLOR, + }, + { id: managedTagId, overwrite: true, refresh: false } + ); + + return managedTagId; +} + +async function ensurePackageTag( + opts: Pick<TagAssetsParams, 'spaceId' | 'savedObjectTagClient' | 'pkgName' | 'pkgTitle'> +): Promise<string> { + const { spaceId, savedObjectTagClient, pkgName, pkgTitle } = opts; + + const packageTagId = getPackageTagId(spaceId, pkgName); + const packageTag = await savedObjectTagClient.get(packageTagId).catch(() => {}); + + if (packageTag) return packageTagId; + + const legacyPackageTagId = getLegacyPackageTagId(pkgName); + const legacyPackageTag = await savedObjectTagClient.get(legacyPackageTagId).catch(() => {}); + + if (legacyPackageTag) return legacyPackageTagId; + + await savedObjectTagClient.create( + { + name: pkgTitle, + description: '', + color: TAG_COLOR, + }, + { id: packageTagId, overwrite: true, refresh: false } + ); + + return packageTagId; } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index 4ecec17560731..78683ecd07e0a 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -133,6 +133,7 @@ export async function _installPackage({ paths, installedPkg, logger, + spaceId, }) ); // Necessary to avoid async promise rejection warning diff --git a/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts b/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts index 8b1529de93b3c..0ff64789d12ed 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/proxy.ts @@ -5,12 +5,9 @@ * 2.0. */ -import HttpProxyAgent from 'http-proxy-agent'; -import HttpsProxyAgent from 'https-proxy-agent'; -import type { - HttpsProxyAgentOptions, - HttpsProxyAgent as IHttpsProxyAgent, -} from 'https-proxy-agent'; +import { HttpProxyAgent } from 'http-proxy-agent'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import type { HttpsProxyAgentOptions } from 'https-proxy-agent'; import { appContextService } from '../..'; @@ -20,7 +17,7 @@ export interface RegistryProxySettings { proxyRejectUnauthorizedCertificates?: boolean; } -type ProxyAgent = IHttpsProxyAgent | HttpProxyAgent; +type ProxyAgent = HttpsProxyAgent | HttpProxyAgent; type GetProxyAgentParams = RegistryProxySettings & { targetUrl: string }; export function getRegistryProxyUrl(): string | undefined { @@ -30,11 +27,10 @@ export function getRegistryProxyUrl(): string | undefined { export function getProxyAgent(options: GetProxyAgentParams): ProxyAgent { const isHttps = options.targetUrl.startsWith('https:'); - const agentOptions = isHttps && getProxyAgentOptions(options); + const agentOptions = isHttps ? getProxyAgentOptions(options) : options.proxyUrl; const agent: ProxyAgent = isHttps - ? // @ts-expect-error ts(7009) HttpsProxyAgent isn't a class so TS complains about using `new` - new HttpsProxyAgent(agentOptions) - : new HttpProxyAgent(options.proxyUrl); + ? new HttpsProxyAgent(agentOptions) + : new HttpProxyAgent(agentOptions); return agent; } diff --git a/x-pack/plugins/fleet/server/services/package_policies/package_policy_name_helper.ts b/x-pack/plugins/fleet/server/services/package_policies/package_policy_name_helper.ts index c712ccc06ad92..90db39a3e03b0 100644 --- a/x-pack/plugins/fleet/server/services/package_policies/package_policy_name_helper.ts +++ b/x-pack/plugins/fleet/server/services/package_policies/package_policy_name_helper.ts @@ -39,20 +39,23 @@ export async function incrementPackagePolicyCopyName( // find all pacakge policies starting with the same name and increment the name const packagePolicyData = await packagePolicyService.list(soClient, { perPage: SO_SEARCH_LIMIT, - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: ${packageName}*`, + // split package name on first space as KQL do not support wildcard and space + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: ${packageName.split(' ')[0]}*`, }); const maxVersion = packagePolicyData.items.length > 0 ? Math.max( - ...packagePolicyData.items.map((item) => { - const matches = item.name.match(/^(.*)\s\(copy\s?([0-9]*)\)$/); - if (matches) { - return parseInt(matches[2], 10) || 1; - } + ...packagePolicyData.items + .filter((item) => item.name.startsWith(packageName)) + .map((item) => { + const matches = item.name.match(/^(.*)\s\(copy\s?([0-9]*)\)$/); + if (matches) { + return parseInt(matches[2], 10) || 1; + } - return 0; - }) + return 0; + }) ) : 0; diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx index 78f38087b9ad1..2bd37e0448b6c 100644 --- a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx @@ -42,7 +42,7 @@ const createResult = (result: Result): GlobalSearchResult => { const createBatch = (...results: Result[]): GlobalSearchBatchedResults => ({ results: results.map(createResult), }); -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); describe('SearchBar', () => { let searchService: ReturnType<typeof globalSearchPluginMock.createStartContract>; diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/edit_warning.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/edit_warning.test.ts index 98d6078da031c..39417219cddb9 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/edit_warning.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/edit_warning.test.ts @@ -16,7 +16,7 @@ describe('<EditPolicy /> edit warning', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/frozen_phase.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/frozen_phase.test.ts index ffe11133be7fd..5b08b4225916b 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/frozen_phase.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/frozen_phase.test.ts @@ -17,7 +17,7 @@ describe('<EditPolicy /> frozen phase', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts index 75db772ec0926..be83028c81b2f 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts @@ -18,7 +18,7 @@ describe('<EditPolicy /> node allocation cloud-aware behavior', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.test.ts index 63382de45f414..217a6264f5745 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cold_phase.test.ts @@ -14,7 +14,7 @@ describe('<EditPolicy /> node allocation in the cold phase', () => { const { httpSetup, setDelayResponse, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.test.ts index 4830cee8ee237..e9f970fbba207 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/general_behavior.test.ts @@ -24,7 +24,7 @@ describe('<EditPolicy /> node allocation general behavior', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.test.ts index 6f96aaf07da1b..df6e409550be9 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/warm_phase.test.ts @@ -14,7 +14,7 @@ describe('<EditPolicy /> node allocation in the warm phase', () => { const { httpSetup, setDelayResponse, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.test.ts index 61dda6fa65efb..30873b54548eb 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/request_flyout.test.ts @@ -15,7 +15,7 @@ describe('<EditPolicy /> request flyout', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/cold_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/cold_phase_validation.test.ts index e75d2cb72ab28..cee0af407442e 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/cold_phase_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/cold_phase_validation.test.ts @@ -15,7 +15,7 @@ describe('<EditPolicy /> cold phase validation', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/error_indicators.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/error_indicators.test.ts index bd4a2caec0be5..2503b32f0506a 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/error_indicators.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/error_indicators.test.ts @@ -14,7 +14,7 @@ describe('<EditPolicy /> error indicators', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/hot_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/hot_phase_validation.test.ts index 71f83a59360d6..54683f638c746 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/hot_phase_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/hot_phase_validation.test.ts @@ -16,7 +16,7 @@ describe('<EditPolicy /> hot phase validation', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/policy_name_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/policy_name_validation.test.ts index c530f73a66c11..19ee23142cd92 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/policy_name_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/policy_name_validation.test.ts @@ -17,7 +17,7 @@ describe('<EditPolicy /> policy name validation', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/timing.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/timing.test.ts index 5838f04ba70e9..08790cf23bf95 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/timing.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/timing.test.ts @@ -18,7 +18,7 @@ describe('<EditPolicy /> timing validation', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/warm_phase_validation.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/warm_phase_validation.test.ts index 47917b1f8e3d7..6fb079819ec36 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/warm_phase_validation.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/form_validation/warm_phase_validation.test.ts @@ -15,7 +15,7 @@ describe('<EditPolicy /> warm phase validation', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx index 861b1041a4f14..99277bb4cc4b6 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.test.tsx @@ -48,7 +48,7 @@ describe('<TemplateClone />', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); httpRequestsMockHelpers.setLoadTelemetryResponse({}); httpRequestsMockHelpers.setLoadComponentTemplatesResponse([]); httpRequestsMockHelpers.setLoadTemplateResponse(templateToClone.name, templateToClone); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx index 6813398a34ae0..ca42777532acc 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.test.tsx @@ -80,7 +80,7 @@ describe('<TemplateCreate />', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); httpRequestsMockHelpers.setLoadComponentTemplatesResponse(componentTemplates); httpRequestsMockHelpers.setLoadNodesPluginsResponse([]); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx index 4b94cb92c83d0..a99b5476a766f 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.test.tsx @@ -52,7 +52,7 @@ describe('<TemplateEdit />', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); httpRequestsMockHelpers.setLoadComponentTemplatesResponse([]); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/date_range_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/date_range_datatype.test.tsx index 60114223bef0d..1b752807ba1c9 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/date_range_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/date_range_datatype.test.tsx @@ -29,7 +29,7 @@ describe('Mappings editor: date range datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/other_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/other_datatype.test.tsx index 81ca155b022b1..58d063cc658cc 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/other_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/other_datatype.test.tsx @@ -21,7 +21,7 @@ describe('Mappings editor: other datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx index 8c235f2d2d9e9..c35d6c231186d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/point_datatype.test.tsx @@ -28,7 +28,7 @@ describe('Mappings editor: point datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/scaled_float_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/scaled_float_datatype.test.tsx index 17e7317e098cc..bbd0afa495e48 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/scaled_float_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/scaled_float_datatype.test.tsx @@ -31,7 +31,7 @@ describe('Mappings editor: scaled float datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx index 94aea2a3b13af..9724a81d673bf 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/shape_datatype.test.tsx @@ -29,7 +29,7 @@ describe('Mappings editor: shape datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx index db8678478aa3d..38fd47476e931 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/text_datatype.test.tsx @@ -34,7 +34,7 @@ describe('Mappings editor: text datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/version_datatype.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/version_datatype.test.tsx index 5f638ebb31a43..6a61d9bac058b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/version_datatype.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/datatypes/version_datatype.test.tsx @@ -26,7 +26,7 @@ describe('Mappings editor: version datatype', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx index 4440f54f1034b..7403be97c6644 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/edit_field.test.tsx @@ -21,7 +21,7 @@ describe('Mappings editor: edit field', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx index eb219503424b3..fe32e3b6099f0 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mapped_fields.test.tsx @@ -14,7 +14,7 @@ const onChangeHandler = jest.fn(); describe('Mappings editor: mapped fields', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 9b4f31f3dfc16..c296361f3685b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -21,7 +21,7 @@ describe('Mappings editor: core', () => { let testBed: MappingsEditorTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/runtime_fields.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/runtime_fields.test.tsx index 76e5dcda8fc44..d1b85685588d0 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/runtime_fields.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/runtime_fields.test.tsx @@ -20,7 +20,7 @@ describe('Mappings editor: runtime fields', () => { let getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/infra/common/http_api/snapshot_api.ts b/x-pack/plugins/infra/common/http_api/snapshot_api.ts index 9c4fd65ce3fec..eb39f6a440a79 100644 --- a/x-pack/plugins/infra/common/http_api/snapshot_api.ts +++ b/x-pack/plugins/infra/common/http_api/snapshot_api.ts @@ -17,6 +17,9 @@ export const SnapshotNodePathRT = rt.intersection([ rt.partial({ ip: rt.union([rt.string, rt.null]), }), + rt.partial({ + os: rt.union([rt.string, rt.null]), + }), ]); const SnapshotNodeMetricOptionalRT = rt.partial({ diff --git a/x-pack/plugins/infra/common/inventory_models/host/index.ts b/x-pack/plugins/infra/common/inventory_models/host/index.ts index 8e2582c5e1f6f..7420ca3f15939 100644 --- a/x-pack/plugins/infra/common/inventory_models/host/index.ts +++ b/x-pack/plugins/infra/common/inventory_models/host/index.ts @@ -33,6 +33,7 @@ export const host: InventoryModel = { fields: { id: 'host.name', name: 'host.name', + os: 'host.os.name', ip: 'host.ip', }, metrics, diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts index 2190140cc0a5e..8a60c4a066502 100644 --- a/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/index.ts @@ -6,10 +6,12 @@ */ import { cpu } from './snapshot/cpu'; +import { cpuCores } from './snapshot/cpu_cores'; import { count } from '../../shared/metrics/snapshot/count'; import { load } from './snapshot/load'; import { logRate } from './snapshot/log_rate'; import { memory } from './snapshot/memory'; +import { memoryTotal } from './snapshot/memory_total'; import { rx } from './snapshot/rx'; import { tx } from './snapshot/tx'; @@ -33,7 +35,7 @@ import { hostDockerInfo } from './tsvb/host_docker_info'; import { InventoryMetrics } from '../../types'; -const exposedHostSnapshotMetrics = { cpu, load, logRate, memory, rx, tx }; +const exposedHostSnapshotMetrics = { cpu, load, logRate, memory, rx, tx, cpuCores, memoryTotal }; // not sure why this is the only model with "count" const hostSnapshotMetrics = { count, ...exposedHostSnapshotMetrics }; diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/cpu_cores.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/cpu_cores.ts new file mode 100644 index 0000000000000..3cc90ebfdca95 --- /dev/null +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/cpu_cores.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MetricsUIAggregation } from '../../../types'; + +export const cpuCores: MetricsUIAggregation = { + cpuCores: { + max: { + field: 'system.cpu.cores', + }, + }, +}; diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/memory_total.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/memory_total.ts new file mode 100644 index 0000000000000..c3d0a8063b93f --- /dev/null +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/memory_total.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 { MetricsUIAggregation } from '../../../types'; + +export const memoryTotal: MetricsUIAggregation = { + memory_total: { + avg: { + field: 'system.memory.total', + }, + }, + memoryTotal: { + bucket_script: { + buckets_path: { + memoryTotal: 'memory_total', + }, + script: { + source: 'params.memoryTotal / 1000000', // Convert to MB + lang: 'painless', + }, + gap_policy: 'skip', + }, + }, +}; diff --git a/x-pack/plugins/infra/common/inventory_models/types.ts b/x-pack/plugins/infra/common/inventory_models/types.ts index 9459933a489d3..8c0118d40fa4b 100644 --- a/x-pack/plugins/infra/common/inventory_models/types.ts +++ b/x-pack/plugins/infra/common/inventory_models/types.ts @@ -339,8 +339,10 @@ export type MetricsUIAggregation = rt.TypeOf<typeof MetricsUIAggregationRT>; export const SnapshotMetricTypeKeys = { count: null, cpu: null, + cpuCores: null, load: null, memory: null, + memoryTotal: null, tx: null, rx: null, logRate: null, @@ -382,6 +384,7 @@ export interface InventoryModel { fields: { id: string; name: string; + os?: string; ip?: string; }; crosslinkSupport: { diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json index 4290fb0382cc6..2226c89ab90f6 100644 --- a/x-pack/plugins/infra/kibana.json +++ b/x-pack/plugins/infra/kibana.json @@ -15,8 +15,7 @@ "triggersActionsUi", "observability", "ruleRegistry", - "unifiedSearch", - "lens" + "unifiedSearch" ], "optionalPlugins": ["ml", "home", "embeddable", "osquery"], "server": true, diff --git a/x-pack/plugins/infra/public/apps/metrics_app.tsx b/x-pack/plugins/infra/public/apps/metrics_app.tsx index ce8123b5f2223..159c6dab7a66e 100644 --- a/x-pack/plugins/infra/public/apps/metrics_app.tsx +++ b/x-pack/plugins/infra/public/apps/metrics_app.tsx @@ -47,7 +47,6 @@ export const renderApp = ( return () => { core.chrome.docTitle.reset(); ReactDOM.unmountComponentAtNode(element); - plugins.data.search.session.clear(); }; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx new file mode 100644 index 0000000000000..036d22d8b7c5f --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx @@ -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 React from 'react'; +import { EuiSpacer } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { InfraLoadingPanel } from '../../../../components/loading'; +import { useMetricsDataViewContext } from '../hooks/use_data_view'; +import { UnifiedSearchBar } from './unified_search_bar'; +import { HostsTable } from './hosts_table'; + +export const HostContainer = () => { + const { metricsDataView, isDataViewLoading, hasFailedLoadingDataView } = + useMetricsDataViewContext(); + + if (isDataViewLoading) { + return ( + <InfraLoadingPanel + height="100%" + width="auto" + text={i18n.translate('xpack.infra.waffle.loadingDataText', { + defaultMessage: 'Loading data', + })} + /> + ); + } + + return hasFailedLoadingDataView || !metricsDataView ? null : ( + <> + <UnifiedSearchBar dataView={metricsDataView} /> + <EuiSpacer /> + <HostsTable /> + </> + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx index 143f7fedb1420..759c65ca84b2e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx @@ -5,549 +5,88 @@ * 2.0. */ -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; -import type { Query, TimeRange } from '@kbn/es-query'; -import React, { useState } from 'react'; -import type { DataView } from '@kbn/data-views-plugin/public'; +import React from 'react'; +import { EuiInMemoryTable } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { HostsTableColumns } from './hosts_table_columns'; import { NoData } from '../../../../components/empty_states'; -import { InfraClientStartDeps } from '../../../../types'; +import { InfraLoadingPanel } from '../../../../components/loading'; +import { useHostTable } from '../hooks/use_host_table'; +import { useSnapshot } from '../../inventory_view/hooks/use_snaphot'; +import type { SnapshotMetricType } from '../../../../../common/inventory_models/types'; +import type { InfraTimerangeInput } from '../../../../../common/http_api'; +import { useUnifiedSearchContext } from '../hooks/use_unified_search'; +import { useSourceContext } from '../../../../containers/metrics_source'; -const getLensHostsTable = ( - metricsDataView: DataView, - query: Query -): TypedLensByValueInput['attributes'] => - ({ - visualizationType: 'lnsDatatable', - title: 'Lens visualization', - references: [ - { - id: metricsDataView.id, - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: metricsDataView.id, - name: 'indexpattern-datasource-layer-cbe5d8a0-381d-49bf-b8ac-f8f312ec7129', - type: 'index-pattern', - }, - ], - state: { - datasourceStates: { - formBased: { - layers: { - 'cbe5d8a0-381d-49bf-b8ac-f8f312ec7129': { - columns: { - '8d17458d-31af-41d1-a23c-5180fd960bee': { - label: 'Name', - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: 'host.name', - isBucketed: true, - params: { - size: 10000, - orderBy: { - type: 'column', - columnId: '467de550-9186-4e18-8cfa-bce07087801a', - }, - orderDirection: 'desc', - otherBucket: true, - missingBucket: false, - parentFormat: { - id: 'terms', - }, - }, - customLabel: true, - }, - '155fc728-d010-498e-8126-0bc46cad2be2': { - label: 'Operating system', - dataType: 'string', - operationType: 'terms', - scale: 'ordinal', - sourceField: 'host.os.type', - isBucketed: true, - params: { - size: 10000, - orderBy: { - type: 'column', - columnId: '467de550-9186-4e18-8cfa-bce07087801a', - }, - orderDirection: 'desc', - otherBucket: false, - missingBucket: false, - parentFormat: { - id: 'terms', - }, - }, - customLabel: true, - }, - '467de550-9186-4e18-8cfa-bce07087801a': { - label: '# of CPUs', - dataType: 'number', - operationType: 'max', - sourceField: 'system.cpu.cores', - isBucketed: false, - scale: 'ratio', - params: { - emptyAsNull: true, - }, - customLabel: true, - }, - '3eca2307-228e-4842-a023-57e15c8c364d': { - label: 'Disk latency (avg.)', - dataType: 'number', - operationType: 'formula', - isBucketed: false, - scale: 'ratio', - params: { - formula: 'average(system.diskio.io.time) / 1000', - isFormulaBroken: false, - format: { - id: 'number', - params: { - decimals: 0, - suffix: 'ms', - }, - }, - }, - references: ['3eca2307-228e-4842-a023-57e15c8c364dX1'], - customLabel: true, - }, - '0a9bd0fa-9966-489b-8c95-70997a7aad4c': { - label: 'Memory total (avg.)', - dataType: 'number', - operationType: 'formula', - isBucketed: false, - scale: 'ratio', - params: { - formula: 'average(system.memory.total)', - isFormulaBroken: false, - format: { - id: 'bytes', - params: { - decimals: 0, - }, - }, - }, - references: ['0a9bd0fa-9966-489b-8c95-70997a7aad4cX0'], - customLabel: true, - }, - 'fe5a4d7d-6f48-45ab-974c-96bc864ac36f': { - label: 'Memory usage (avg.)', - dataType: 'number', - operationType: 'formula', - isBucketed: false, - scale: 'ratio', - params: { - formula: 'average(system.memory.used.pct)', - isFormulaBroken: false, - format: { - id: 'percent', - params: { - decimals: 0, - }, - }, - }, - references: ['fe5a4d7d-6f48-45ab-974c-96bc864ac36fX0'], - customLabel: true, - }, - '0a9bd0fa-9966-489b-8c95-70997a7aad4cX0': { - label: 'Part of Memory Total (avg)', - dataType: 'number', - operationType: 'average', - sourceField: 'system.memory.total', - isBucketed: false, - scale: 'ratio', - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - 'fe5a4d7d-6f48-45ab-974c-96bc864ac36fX0': { - label: 'Part of Memory Usage (avg)', - dataType: 'number', - operationType: 'average', - sourceField: 'system.memory.used.pct', - isBucketed: false, - scale: 'ratio', - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '3eca2307-228e-4842-a023-57e15c8c364dX0': { - label: 'Part of Disk Latency (avg ms)', - dataType: 'number', - operationType: 'average', - sourceField: 'system.diskio.io.time', - isBucketed: false, - scale: 'ratio', - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '3eca2307-228e-4842-a023-57e15c8c364dX1': { - label: 'Part of Disk Latency (avg ms)', - dataType: 'number', - operationType: 'math', - isBucketed: false, - scale: 'ratio', - params: { - tinymathAst: { - type: 'function', - name: 'divide', - args: ['3eca2307-228e-4842-a023-57e15c8c364dX0', 1000], - location: { - min: 0, - max: 37, - }, - text: 'average(system.diskio.io.time) / 1000', - }, - }, - references: ['3eca2307-228e-4842-a023-57e15c8c364dX0'], - customLabel: true, - }, - '02e9d54c-bbe0-42dc-839c-55080a29838dX0': { - label: 'Part of RX (avg)', - dataType: 'number', - operationType: 'average', - sourceField: 'host.network.ingress.bytes', - isBucketed: false, - scale: 'ratio', - filter: { - query: 'host.network.ingress.bytes: *', - language: 'kuery', - }, - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '02e9d54c-bbe0-42dc-839c-55080a29838dX1': { - label: 'Part of RX (avg)', - dataType: 'number', - operationType: 'max', - sourceField: 'metricset.period', - isBucketed: false, - scale: 'ratio', - filter: { - query: 'host.network.ingress.bytes: *', - language: 'kuery', - }, - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '02e9d54c-bbe0-42dc-839c-55080a29838dX2': { - label: 'Part of RX (avg)', - dataType: 'number', - operationType: 'math', - isBucketed: false, - scale: 'ratio', - params: { - tinymathAst: { - type: 'function', - name: 'divide', - args: [ - { - type: 'function', - name: 'multiply', - args: ['02e9d54c-bbe0-42dc-839c-55080a29838dX0', 8], - location: { - min: 1, - max: 40, - }, - text: 'average(host.network.ingress.bytes) * 8', - }, - { - type: 'function', - name: 'divide', - args: ['02e9d54c-bbe0-42dc-839c-55080a29838dX1', 1000], - location: { - min: 45, - max: 73, - }, - text: 'max(metricset.period) / 1000', - }, - ], - location: { - min: 0, - max: 75, - }, - text: '(average(host.network.ingress.bytes) * 8) / (max(metricset.period) / 1000)\n', - }, - }, - references: [ - '02e9d54c-bbe0-42dc-839c-55080a29838dX0', - '02e9d54c-bbe0-42dc-839c-55080a29838dX1', - ], - customLabel: true, - }, - '02e9d54c-bbe0-42dc-839c-55080a29838d': { - label: 'RX (avg.)', - dataType: 'number', - operationType: 'formula', - isBucketed: false, - scale: 'ratio', - params: { - formula: - '(average(host.network.ingress.bytes) * 8) / (max(metricset.period) / 1000)\n', - isFormulaBroken: false, - format: { - id: 'bits', - params: { - decimals: 0, - suffix: '/s', - }, - }, - }, - references: ['02e9d54c-bbe0-42dc-839c-55080a29838dX2'], - filter: { - query: 'host.network.ingress.bytes: *', - language: 'kuery', - }, - customLabel: true, - }, - '7802ef93-622c-44df-94fa-03eec01bb7b6X0': { - label: 'Part of TX', - dataType: 'number', - operationType: 'average', - sourceField: 'host.network.egress.bytes', - isBucketed: false, - scale: 'ratio', - filter: { - query: 'host.network.egress.bytes: *', - language: 'kuery', - }, - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '7802ef93-622c-44df-94fa-03eec01bb7b6X1': { - label: 'Part of TX', - dataType: 'number', - operationType: 'max', - sourceField: 'metricset.period', - isBucketed: false, - scale: 'ratio', - filter: { - query: 'host.network.egress.bytes: *', - language: 'kuery', - }, - params: { - emptyAsNull: false, - }, - customLabel: true, - }, - '7802ef93-622c-44df-94fa-03eec01bb7b6X2': { - label: 'Part of TX', - dataType: 'number', - operationType: 'math', - isBucketed: false, - scale: 'ratio', - params: { - tinymathAst: { - type: 'function', - name: 'divide', - args: [ - { - type: 'function', - name: 'multiply', - args: ['7802ef93-622c-44df-94fa-03eec01bb7b6X0', 8], - location: { - min: 1, - max: 39, - }, - text: 'average(host.network.egress.bytes) * 8', - }, - { - type: 'function', - name: 'divide', - args: ['7802ef93-622c-44df-94fa-03eec01bb7b6X1', 1000], - location: { - min: 44, - max: 72, - }, - text: 'max(metricset.period) / 1000', - }, - ], - location: { - min: 0, - max: 74, - }, - text: '(average(host.network.egress.bytes) * 8) / (max(metricset.period) / 1000)\n', - }, - }, - references: [ - '7802ef93-622c-44df-94fa-03eec01bb7b6X0', - '7802ef93-622c-44df-94fa-03eec01bb7b6X1', - ], - customLabel: true, - }, - '7802ef93-622c-44df-94fa-03eec01bb7b6': { - label: 'TX (avg.)', - dataType: 'number', - operationType: 'formula', - isBucketed: false, - scale: 'ratio', - params: { - formula: - '(average(host.network.egress.bytes) * 8) / (max(metricset.period) / 1000)\n', - isFormulaBroken: false, - format: { - id: 'bits', - params: { - decimals: 0, - suffix: '/s', - }, - }, - }, - references: ['7802ef93-622c-44df-94fa-03eec01bb7b6X2'], - filter: { - query: 'host.network.egress.bytes: *', - language: 'kuery', - }, - customLabel: true, - }, - }, - columnOrder: [ - '8d17458d-31af-41d1-a23c-5180fd960bee', - '155fc728-d010-498e-8126-0bc46cad2be2', - '467de550-9186-4e18-8cfa-bce07087801a', - '3eca2307-228e-4842-a023-57e15c8c364d', - '02e9d54c-bbe0-42dc-839c-55080a29838d', - '7802ef93-622c-44df-94fa-03eec01bb7b6', - '0a9bd0fa-9966-489b-8c95-70997a7aad4c', - 'fe5a4d7d-6f48-45ab-974c-96bc864ac36f', - '0a9bd0fa-9966-489b-8c95-70997a7aad4cX0', - 'fe5a4d7d-6f48-45ab-974c-96bc864ac36fX0', - '3eca2307-228e-4842-a023-57e15c8c364dX0', - '3eca2307-228e-4842-a023-57e15c8c364dX1', - '02e9d54c-bbe0-42dc-839c-55080a29838dX0', - '02e9d54c-bbe0-42dc-839c-55080a29838dX1', - '02e9d54c-bbe0-42dc-839c-55080a29838dX2', - '7802ef93-622c-44df-94fa-03eec01bb7b6X0', - '7802ef93-622c-44df-94fa-03eec01bb7b6X1', - '7802ef93-622c-44df-94fa-03eec01bb7b6X2', - ], - incompleteColumns: {}, - indexPatternId: '305688db-9e02-4046-acc1-5d0d8dd73ef6', - }, - }, - }, - }, - visualization: { - layerId: 'cbe5d8a0-381d-49bf-b8ac-f8f312ec7129', - layerType: 'data', - columns: [ - { - columnId: '8d17458d-31af-41d1-a23c-5180fd960bee', - width: 296.16666666666663, - isTransposed: false, - }, - { - columnId: '155fc728-d010-498e-8126-0bc46cad2be2', - isTransposed: false, - width: 152.36666666666667, - }, - { - columnId: '467de550-9186-4e18-8cfa-bce07087801a', - isTransposed: false, - width: 121.11666666666667, - }, - { - columnId: '3eca2307-228e-4842-a023-57e15c8c364d', - isTransposed: false, - }, - { - columnId: '0a9bd0fa-9966-489b-8c95-70997a7aad4c', - isTransposed: false, - }, - { - columnId: 'fe5a4d7d-6f48-45ab-974c-96bc864ac36f', - isTransposed: false, - }, - { - isTransposed: false, - columnId: '02e9d54c-bbe0-42dc-839c-55080a29838d', - }, - { - isTransposed: false, - columnId: '7802ef93-622c-44df-94fa-03eec01bb7b6', - }, - ], - paging: { - size: 10, - enabled: true, - }, - headerRowHeight: 'custom', - headerRowHeightLines: 2, - rowHeight: 'single', - rowHeightLines: 1, - }, - filters: [], - query, - }, - } as TypedLensByValueInput['attributes']); +const HOST_METRICS: Array<{ type: SnapshotMetricType }> = [ + { type: 'rx' }, + { type: 'tx' }, + { type: 'memory' }, + { type: 'cpuCores' }, + { type: 'memoryTotal' }, +]; -interface Props { - dataView: DataView; - timeRange: TimeRange; - query: Query; - searchSessionId: string; - onRefetch: () => void; - onLoading: (isLoading: boolean) => void; - isLensLoading: boolean; -} -export const HostsTable: React.FunctionComponent<Props> = ({ - dataView, - timeRange, - query, - searchSessionId, - onRefetch, - onLoading, - isLensLoading, -}) => { - const { - services: { lens }, - } = useKibana<InfraClientStartDeps>(); - const LensComponent = lens?.EmbeddableComponent; - const [noData, setNoData] = useState(false); +export const HostsTable = () => { + const { sourceId } = useSourceContext(); + const { esQuery, dateRangeTimestamp } = useUnifiedSearchContext(); + + const timeRange: InfraTimerangeInput = { + from: dateRangeTimestamp.from, + to: dateRangeTimestamp.to, + interval: '1m', + ignoreLookback: true, + }; + + // Snapshot endpoint internally uses the indices stored in source.configuration.metricAlias. + // For the Unified Search, we create a data view, which for now will be built off of source.configuration.metricAlias too + // if we introduce data view selection, we'll have to change this hook and the endpoint to accept a new parameter for the indices + const { loading, nodes, reload } = useSnapshot( + esQuery && JSON.stringify(esQuery), + HOST_METRICS, + [], + 'host', + sourceId, + dateRangeTimestamp.to, + '', + '', + true, + timeRange + ); + + const items = useHostTable(nodes); + const noData = items.length === 0; - if (noData && !isLensLoading) { - return ( - <NoData - titleText={i18n.translate('xpack.infra.metrics.emptyViewTitle', { - defaultMessage: 'There is no data to display.', - })} - bodyText={i18n.translate('xpack.infra.metrics.emptyViewDescription', { - defaultMessage: 'Try adjusting your time or filter.', - })} - refetchText={i18n.translate('xpack.infra.metrics.refetchButtonLabel', { - defaultMessage: 'Check for new data', - })} - onRefetch={onRefetch} - testString="metricsEmptyViewState" - /> - ); - } return ( - <LensComponent - id="hostsView" - timeRange={timeRange} - attributes={getLensHostsTable(dataView, query)} - searchSessionId={searchSessionId} - onLoad={(isLoading, adapters) => { - if (!isLoading && adapters?.tables) { - setNoData(adapters?.tables.tables.default?.rows.length === 0); - onLoading(false); - } - }} - /> + <> + {loading ? ( + <InfraLoadingPanel + height="100%" + width="auto" + text={i18n.translate('xpack.infra.waffle.loadingDataText', { + defaultMessage: 'Loading data', + })} + /> + ) : noData ? ( + <div> + <NoData + titleText={i18n.translate('xpack.infra.waffle.noDataTitle', { + defaultMessage: 'There is no data to display.', + })} + bodyText={i18n.translate('xpack.infra.waffle.noDataDescription', { + defaultMessage: 'Try adjusting your time or filter.', + })} + refetchText={i18n.translate('xpack.infra.waffle.checkNewDataButtonLabel', { + defaultMessage: 'Check for new data', + })} + onRefetch={() => { + reload(); + }} + testString="noMetricsDataPrompt" + /> + </div> + ) : ( + <EuiInMemoryTable pagination sorting items={items} columns={HostsTableColumns} /> + )} + </> ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx new file mode 100644 index 0000000000000..04d035b5fb7eb --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.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 { EuiBasicTableColumn } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiText } from '@elastic/eui'; +import type { SnapshotNodeMetric } from '../../../../../common/http_api'; +import { scaleUpPercentage } from '../../../../components/infrastructure_node_metrics_tables/shared/hooks'; +import { NumberCell } from '../../../../components/infrastructure_node_metrics_tables/shared/components'; + +interface HostNodeRow extends HostMetics { + os?: string | null; + servicesOnHost?: number | null; + name: string; +} + +export interface HostMetics { + cpuCores: SnapshotNodeMetric; + rx: SnapshotNodeMetric; + tx: SnapshotNodeMetric; + memory: SnapshotNodeMetric; + memoryTotal: SnapshotNodeMetric; +} + +export const HostsTableColumns: Array<EuiBasicTableColumn<HostNodeRow>> = [ + { + name: i18n.translate('xpack.infra.hostsTable.nameColumnHeader', { + defaultMessage: 'Name', + }), + field: 'name', + sortable: true, + truncateText: true, + textOnly: true, + render: (name: string) => <EuiText size="s">{name}</EuiText>, + }, + { + name: i18n.translate('xpack.infra.hostsTable.operatingSystemColumnHeader', { + defaultMessage: 'Operating System', + }), + field: 'os', + sortable: true, + render: (os: string) => <EuiText size="s">{os}</EuiText>, + }, + { + name: i18n.translate('xpack.infra.hostsTable.numberOfCpusColumnHeader', { + defaultMessage: '# of CPUs', + }), + field: 'cpuCores.value', + sortable: true, + render: (value: number) => <NumberCell value={value} />, + }, + { + name: i18n.translate('xpack.infra.hostsTable.diskLatencyColumnHeader', { + defaultMessage: 'Disk Latency', + }), + field: 'diskLatency', + sortable: true, + render: (ds: number) => <NumberCell value={ds} unit=" ms" />, + }, + { + name: i18n.translate('xpack.infra.hostsTable.averageTxColumnHeader', { + defaultMessage: 'TX (avg.)', + }), + field: 'tx.avg', + sortable: true, + render: (avg: number) => <NumberCell value={avg} />, + }, + { + name: i18n.translate('xpack.infra.hostsTable.averageRxColumnHeader', { + defaultMessage: 'RX (avg.)', + }), + field: 'rx.avg', + sortable: true, + render: (avg: number) => <NumberCell value={avg} />, + }, + { + name: i18n.translate('xpack.infra.hostsTable.averageMemoryTotalColumnHeader', { + defaultMessage: 'Memory total (avg.)', + }), + field: 'memoryTotal.avg', + sortable: true, + render: (avg: number) => <NumberCell value={Math.floor(avg)} unit=" MB" />, + }, + { + name: i18n.translate('xpack.infra.hostsTable.servicesOnHostColumnHeader', { + defaultMessage: 'Services on Host', + }), + field: 'servicesOnHost', + sortable: true, + render: (servicesOnHost: number) => <NumberCell value={servicesOnHost} />, + }, + { + name: i18n.translate('xpack.infra.hostsTable.averageMemoryUsageColumnHeader', { + defaultMessage: 'Memory usage (avg.)', + }), + field: 'memory.avg', + sortable: true, + render: (avg: number) => <NumberCell value={scaleUpPercentage(avg)} unit="%" />, + }, +]; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx new file mode 100644 index 0000000000000..ec9879579908e --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.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 React from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import type { SavedQuery } from '@kbn/data-plugin/public'; +import type { InfraClientStartDeps } from '../../../../types'; +import { useUnifiedSearchContext } from '../hooks/use_unified_search'; + +interface Props { + dataView: DataView; +} + +export const UnifiedSearchBar = ({ dataView }: Props) => { + const { + services: { unifiedSearch }, + } = useKibana<InfraClientStartDeps>(); + const { + unifiedSearchDateRange, + unifiedSearchQuery, + submitFilterChange, + saveQuery, + clearSavedQUery, + } = useUnifiedSearchContext(); + + const { SearchBar } = unifiedSearch.ui; + + const onFilterChange = (filters: Filter[]) => { + onQueryChange({ filters }); + }; + + const onQuerySubmit = (payload: { dateRange: TimeRange; query?: Query }) => { + onQueryChange({ payload }); + }; + + const onClearSavedQuery = () => { + clearSavedQUery(); + }; + + const onQuerySave = (savedQuery: SavedQuery) => { + saveQuery(savedQuery); + }; + + const onQueryChange = ({ + payload, + filters, + }: { + payload?: { dateRange: TimeRange; query?: Query }; + filters?: Filter[]; + }) => { + submitFilterChange(payload?.query, payload?.dateRange, filters); + }; + + return ( + <SearchBar + appName={'Infra Hosts'} + indexPatterns={[dataView]} + query={unifiedSearchQuery} + dateRangeFrom={unifiedSearchDateRange.from} + dateRangeTo={unifiedSearchDateRange.to} + onQuerySubmit={onQuerySubmit} + onSaved={onQuerySave} + onSavedQueryUpdated={onQuerySave} + onClearSavedQuery={onClearSavedQuery} + showSaveQuery + showQueryInput + // @ts-expect-error onFiltersUpdated is a valid prop on SearchBar + onFiltersUpdated={onFilterChange} + /> + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.test.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.test.ts new file mode 100644 index 0000000000000..2a2bb57b102ff --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.test.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useDataView } from './use_data_view'; +import { renderHook } from '@testing-library/react-hooks'; +import { KibanaReactContextValue, useKibana } from '@kbn/kibana-react-plugin/public'; +import { coreMock, notificationServiceMock } from '@kbn/core/public/mocks'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { DataViewsServicePublic } from '@kbn/data-views-plugin/public/types'; +import { InfraClientStartDeps } from '../../../../types'; +import { CoreStart } from '@kbn/core/public'; + +jest.mock('@kbn/i18n'); +jest.mock('@kbn/kibana-react-plugin/public'); + +let dataViewMock: jest.Mocked<DataViewsServicePublic>; +const useKibanaMock = useKibana as jest.MockedFunction<typeof useKibana>; +const notificationMock = notificationServiceMock.createStartContract(); +const prop = { metricAlias: 'test' }; + +const mockUseKibana = () => { + useKibanaMock.mockReturnValue({ + services: { + ...coreMock.createStart(), + notifications: notificationMock, + dataViews: dataViewMock, + } as Partial<CoreStart> & Partial<InfraClientStartDeps>, + } as unknown as KibanaReactContextValue<Partial<CoreStart> & Partial<InfraClientStartDeps>>); +}; + +const mockDataView = { + id: 'mock-id', + title: 'mock-title', + timeFieldName: 'mock-time-field-name', + isPersisted: () => false, + getName: () => 'mock-data-view', + toSpec: () => ({}), +} as jest.Mocked<DataView>; + +describe('useHostTable hook', () => { + beforeEach(() => { + dataViewMock = { + createAndSave: jest.fn(), + find: jest.fn(), + } as Partial<DataViewsServicePublic> as jest.Mocked<DataViewsServicePublic>; + + mockUseKibana(); + }); + + it('should find an existing Data view', async () => { + dataViewMock.find.mockReturnValue(Promise.resolve([mockDataView])); + const { result, waitForNextUpdate } = renderHook(() => useDataView(prop)); + + await waitForNextUpdate(); + expect(result.current.isDataViewLoading).toEqual(false); + expect(result.current.hasFailedLoadingDataView).toEqual(false); + expect(result.current.metricsDataView).toEqual(mockDataView); + }); + + it('should create a new Data view', async () => { + dataViewMock.find.mockReturnValue(Promise.resolve([])); + dataViewMock.createAndSave.mockReturnValue(Promise.resolve(mockDataView)); + const { result, waitForNextUpdate } = renderHook(() => useDataView(prop)); + + await waitForNextUpdate(); + expect(result.current.isDataViewLoading).toEqual(false); + expect(result.current.hasFailedLoadingDataView).toEqual(false); + expect(result.current.metricsDataView).toEqual(mockDataView); + }); + + it('should display a toast when it fails to load the data view', async () => { + dataViewMock.find.mockReturnValue(Promise.reject()); + const { result, waitForNextUpdate } = renderHook(() => useDataView(prop)); + + await waitForNextUpdate(); + expect(result.current.isDataViewLoading).toEqual(false); + expect(result.current.hasFailedLoadingDataView).toEqual(true); + expect(result.current.metricsDataView).toBeUndefined(); + expect(notificationMock.toasts.addDanger).toBeCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts index b60b2aa89db62..f927afa72890c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { useCallback, useState, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useCallback, useState, useEffect, useMemo } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import createContainer from 'constate'; import type { DataView } from '@kbn/data-views-plugin/public'; @@ -15,7 +16,7 @@ import { useTrackedPromise } from '../../../../utils/use_tracked_promise'; export const useDataView = ({ metricAlias }: { metricAlias: string }) => { const [metricsDataView, setMetricsDataView] = useState<DataView>(); const { - services: { dataViews }, + services: { dataViews, notifications }, } = useKibana<InfraClientStartDeps>(); const [createDataViewRequest, createDataView] = useTrackedPromise( @@ -33,7 +34,7 @@ export const useDataView = ({ metricAlias }: { metricAlias: string }) => { const [getDataViewRequest, getDataView] = useTrackedPromise( { - createPromise: (indexPattern: string): Promise<DataView[]> => { + createPromise: (_indexPattern: string): Promise<DataView[]> => { return dataViews.find(metricAlias, 1); }, onResolve: (response: DataView[]) => { @@ -58,17 +59,36 @@ export const useDataView = ({ metricAlias }: { metricAlias: string }) => { } }, [metricAlias, createDataView, getDataView]); - const hasFailedFetchingDataView = getDataViewRequest.state === 'rejected'; - const hasFailedCreatingDataView = createDataViewRequest.state === 'rejected'; + const isDataViewLoading = useMemo( + () => getDataViewRequest.state === 'pending' || createDataViewRequest.state === 'pending', + [getDataViewRequest.state, createDataViewRequest.state] + ); + + const hasFailedLoadingDataView = useMemo( + () => getDataViewRequest.state === 'rejected' || createDataViewRequest.state === 'rejected', + [getDataViewRequest.state, createDataViewRequest.state] + ); useEffect(() => { loadDataView(); }, [metricAlias, loadDataView]); + useEffect(() => { + if (hasFailedLoadingDataView && notifications) { + notifications.toasts.addDanger( + i18n.translate('xpack.infra.hostsTable.errorOnCreateOrLoadDataview', { + defaultMessage: + 'There was an error trying to load or create the Data View: {metricAlias}', + values: { metricAlias }, + }) + ); + } + }, [hasFailedLoadingDataView, notifications, metricAlias]); + return { metricsDataView, - hasFailedCreatingDataView, - hasFailedFetchingDataView, + isDataViewLoading, + hasFailedLoadingDataView, }; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.test.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.test.ts new file mode 100644 index 0000000000000..81b0e93f10121 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.test.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 { useHostTable } from './use_host_table'; +import { renderHook } from '@testing-library/react-hooks'; + +describe('useHostTable hook', () => { + it('it should map the nodes returned from the snapshot api to a format matching eui table items', () => { + const nodes = [ + { + metrics: [ + { + name: 'rx', + avg: 252456.92916666667, + }, + { + name: 'tx', + avg: 252758.425, + }, + { + name: 'memory', + avg: 0.94525, + }, + { + name: 'cpuCores', + value: 10, + }, + { + name: 'memoryTotal', + avg: 34359.738368, + }, + ], + path: [{ value: 'host-0', label: 'host-0', os: null }], + name: 'host-0', + }, + { + metrics: [ + { + name: 'rx', + avg: 95.86339715321859, + }, + { + name: 'tx', + avg: 110.38566859563191, + }, + { + name: 'memory', + avg: 0.5400000214576721, + }, + { + name: 'cpuCores', + value: 8, + }, + { + name: 'memoryTotal', + avg: 9.194304, + }, + ], + path: [ + { value: 'host-1', label: 'host-1' }, + { value: 'host-1', label: 'host-1', ip: '243.86.94.22', os: 'macOS' }, + ], + name: 'host-1', + }, + ]; + + const items = [ + { + name: 'host-0', + os: '-', + rx: { + name: 'rx', + avg: 252456.92916666667, + }, + tx: { + name: 'tx', + avg: 252758.425, + }, + memory: { + name: 'memory', + avg: 0.94525, + }, + cpuCores: { + name: 'cpuCores', + value: 10, + }, + memoryTotal: { + name: 'memoryTotal', + + avg: 34359.738368, + }, + }, + { + name: 'host-1', + os: 'macOS', + rx: { + name: 'rx', + avg: 95.86339715321859, + }, + tx: { + name: 'tx', + avg: 110.38566859563191, + }, + memory: { + name: 'memory', + avg: 0.5400000214576721, + }, + cpuCores: { + name: 'cpuCores', + value: 8, + }, + memoryTotal: { + name: 'memoryTotal', + avg: 9.194304, + }, + }, + ]; + const result = renderHook(() => useHostTable(nodes)); + + expect(result.result.current).toStrictEqual(items); + }); +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.ts new file mode 100644 index 0000000000000..32eed5e54ce6b --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_host_table.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 { useMemo } from 'react'; +import type { SnapshotNode, SnapshotNodeMetric } from '../../../../../common/http_api'; +import { HostMetics } from '../components/hosts_table_columns'; + +type MappedMetrics = Record<keyof HostMetics, SnapshotNodeMetric>; + +export const useHostTable = (nodes: SnapshotNode[]) => { + const items = useMemo(() => { + return nodes.map(({ metrics, path, name }) => ({ + name, + os: path.at(-1)?.os ?? '-', + ...metrics.reduce((data, metric) => { + data[metric.name as keyof HostMetics] = metric; + return data; + }, {} as MappedMetrics), + })); + }, [nodes]); + + return items; +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts new file mode 100644 index 0000000000000..4b3d4e7a47df6 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import createContainer from 'constate'; +import { useCallback, useReducer } from 'react'; +import { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import DateMath from '@kbn/datemath'; +import type { SavedQuery } from '@kbn/data-plugin/public'; +import type { InfraClientStartDeps } from '../../../../types'; +import { useMetricsDataViewContext } from './use_data_view'; +import { useKibanaTimefilterTime } from '../../../../hooks/use_kibana_timefilter_time'; + +const DEFAULT_FROM_MINUTES_VALUE = 15; + +export const useUnifiedSearch = () => { + const [, forceUpdate] = useReducer((x: number) => x + 1, 0); + + const { metricsDataView } = useMetricsDataViewContext(); + const { services } = useKibana<InfraClientStartDeps>(); + const { + data: { query: queryManager }, + } = services; + + const [getTime, setTime] = useKibanaTimefilterTime({ + from: `now-${DEFAULT_FROM_MINUTES_VALUE}m`, + to: 'now', + }); + const { queryString, filterManager } = queryManager; + + const currentDate = new Date(); + const fromTS = + DateMath.parse(getTime().from)?.valueOf() ?? + new Date(currentDate.getMinutes() - DEFAULT_FROM_MINUTES_VALUE).getTime(); + const toTS = DateMath.parse(getTime().to)?.valueOf() ?? currentDate.getTime(); + + const currentTimeRange = { + from: fromTS, + to: toTS, + }; + + const submitFilterChange = useCallback( + (query?: Query, dateRange?: TimeRange, filters?: Filter[]) => { + if (filters) { + filterManager.setFilters(filters); + } + + setTime({ + ...getTime(), + ...dateRange, + }); + + queryString.setQuery({ ...queryString.getQuery(), ...query }); + // Unified search holds the all state, we need to force the hook to rerender so that it can return the most recent values + // This can be removed once we get the state from the URL + forceUpdate(); + }, + [filterManager, queryString, getTime, setTime] + ); + + const saveQuery = useCallback( + (newSavedQuery: SavedQuery) => { + const savedQueryFilters = newSavedQuery.attributes.filters ?? []; + const globalFilters = filterManager.getGlobalFilters(); + filterManager.setFilters([...savedQueryFilters, ...globalFilters]); + + // Unified search holds the all state, we need to force the hook to rerender so that it can return the most recent values + // This can be removed once we get the state from the URL + forceUpdate(); + }, + [filterManager] + ); + + const clearSavedQUery = useCallback(() => { + filterManager.setFilters(filterManager.getGlobalFilters()); + + // Unified search holds the all state, we need to force the hook to rerender so that it can return the most recent values + // This can be removed once we get the state from the URL + forceUpdate(); + }, [filterManager]); + + const buildQuery = useCallback(() => { + if (!metricsDataView) { + return null; + } + return buildEsQuery(metricsDataView, queryString.getQuery(), filterManager.getFilters()); + }, [filterManager, metricsDataView, queryString]); + + return { + dateRangeTimestamp: currentTimeRange, + esQuery: buildQuery(), + submitFilterChange, + saveQuery, + clearSavedQUery, + unifiedSearchQuery: queryString.getQuery() as Query, + unifiedSearchDateRange: getTime(), + unifiedSearchFilters: filterManager.getFilters(), + }; +}; + +export const UnifiedSearch = createContainer(useUnifiedSearch); +export const [UnifiedSearchProvider, useUnifiedSearchContext] = UnifiedSearch; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx deleted file mode 100644 index 2fc841d651e21..0000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx +++ /dev/null @@ -1,100 +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 { Query, TimeRange } from '@kbn/es-query'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { i18n } from '@kbn/i18n'; -import React, { useState, useCallback } from 'react'; -import { SearchBar } from '@kbn/unified-search-plugin/public'; -import { InfraLoadingPanel } from '../../../components/loading'; -import { useMetricsDataViewContext } from './hooks/use_data_view'; -import { HostsTable } from './components/hosts_table'; -import { InfraClientStartDeps } from '../../../types'; -import { useSourceContext } from '../../../containers/metrics_source'; - -export const HostsContent: React.FunctionComponent = () => { - const { - services: { data }, - } = useKibana<InfraClientStartDeps>(); - const { source } = useSourceContext(); - const [dateRange, setDateRange] = useState<TimeRange>({ from: 'now-15m', to: 'now' }); - const [query, setQuery] = useState<Query>({ query: '', language: 'kuery' }); - const { metricsDataView, hasFailedCreatingDataView, hasFailedFetchingDataView } = - useMetricsDataViewContext(); - // needed to refresh the lens table when filters havent changed - const [searchSessionId, setSearchSessionId] = useState(data.search.session.start()); - const [isLensLoading, setIsLensLoading] = useState(false); - - const onQuerySubmit = useCallback( - (payload: { dateRange: TimeRange; query?: Query }) => { - setDateRange(payload.dateRange); - if (payload.query) { - setQuery(payload.query); - } - setIsLensLoading(true); - setSearchSessionId(data.search.session.start()); - }, - [setDateRange, setQuery, data.search.session] - ); - - const onLoading = useCallback( - (isLoading: boolean) => { - if (isLensLoading) { - setIsLensLoading(isLoading); - } - }, - [setIsLensLoading, isLensLoading] - ); - - const onRefetch = useCallback(() => { - setIsLensLoading(true); - setSearchSessionId(data.search.session.start()); - }, [data.search.session]); - - return ( - <div> - {metricsDataView ? ( - <> - <SearchBar - showFilterBar={false} - showDatePicker={true} - showAutoRefreshOnly={false} - showSaveQuery={true} - showQueryInput={true} - query={query} - dateRangeFrom={dateRange.from} - dateRangeTo={dateRange.to} - indexPatterns={[metricsDataView]} - onQuerySubmit={onQuerySubmit} - /> - <HostsTable - dataView={metricsDataView} - timeRange={dateRange} - query={query} - searchSessionId={searchSessionId} - onRefetch={onRefetch} - onLoading={onLoading} - isLensLoading={isLensLoading} - /> - </> - ) : hasFailedCreatingDataView || hasFailedFetchingDataView ? ( - <div> - <div>There was an error trying to load or create the Data View:</div> - {source?.configuration.metricAlias} - </div> - ) : ( - <InfraLoadingPanel - height="100vh" - width="auto" - text={i18n.translate('xpack.infra.waffle.loadingDataText', { - defaultMessage: 'Loading data', - })} - /> - )} - </div> - ); -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx index a5dfd7f2ddd0f..3321be0af193c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx @@ -9,16 +9,16 @@ import { EuiErrorBoundary } from '@elastic/eui'; import React from 'react'; import { useTrackPageview } from '@kbn/observability-plugin/public'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; - import { SourceErrorPage } from '../../../components/source_error_page'; import { SourceLoadingPage } from '../../../components/source_loading_page'; import { useSourceContext } from '../../../containers/metrics_source'; import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; import { MetricsPageTemplate } from '../page_template'; import { hostsTitle } from '../../../translations'; -import { HostsContent } from './hosts_content'; import { MetricsDataViewProvider } from './hooks/use_data_view'; import { fullHeightContentStyles } from '../../../page_template.styles'; +import { UnifiedSearchProvider } from './hooks/use_unified_search'; +import { HostContainer } from './components/hosts_container'; export const HostsPage = () => { const { @@ -56,7 +56,9 @@ export const HostsPage = () => { }} > <MetricsDataViewProvider metricAlias={source.configuration.metricAlias}> - <HostsContent /> + <UnifiedSearchProvider> + <HostContainer /> + </UnifiedSearchProvider> </MetricsDataViewProvider> </MetricsPageTemplate> </div> diff --git a/x-pack/plugins/infra/public/types.ts b/x-pack/plugins/infra/public/types.ts index 461ed1261233a..be8c4d877c61c 100644 --- a/x-pack/plugins/infra/public/types.ts +++ b/x-pack/plugins/infra/public/types.ts @@ -28,7 +28,6 @@ import type { } from '@kbn/observability-plugin/public'; // import type { OsqueryPluginStart } from '../../osquery/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; -import type { LensPublicStart } from '@kbn/lens-plugin/public'; import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { UnwrapPromise } from '../common/utility_types'; import type { @@ -75,7 +74,6 @@ export interface InfraClientStartDeps { embeddable?: EmbeddableStart; osquery?: unknown; // OsqueryPluginStart; share: SharePluginStart; - lens: LensPublicStart; storage: IStorageWrapper; } diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 64d389a1c0bf7..55b847d33f87d 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -22,16 +22,18 @@ import { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; import { PluginSetupContract as AlertingPluginContract } from '@kbn/alerting-plugin/server'; import { MlPluginSetup } from '@kbn/ml-plugin/server'; import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server'; +import { ObservabilityPluginSetup } from '@kbn/observability-plugin/server'; export interface InfraServerPluginSetupDeps { + alerting: AlertingPluginContract; data: DataPluginSetup; home: HomeServerPluginSetup; + features: FeaturesPluginSetup; + ruleRegistry: RuleRegistryPluginSetupContract; + observability: ObservabilityPluginSetup; spaces: SpacesPluginSetup; usageCollection: UsageCollectionSetup; visTypeTimeseries: VisTypeTimeseriesSetup; - features: FeaturesPluginSetup; - alerting: AlertingPluginContract; - ruleRegistry: RuleRegistryPluginSetupContract; ml?: MlPluginSetup; } diff --git a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts index 80dae7ffac959..644c31813deae 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts @@ -169,6 +169,14 @@ export const alertStateActionVariableDescription = i18n.translate( } ); +export const alertDetailUrlActionVariableDescription = i18n.translate( + 'xpack.infra.metrics.alerting.alertDetailUrlActionVariableDescription', + { + defaultMessage: + 'Link to the view within Elastic that shows further details and context surrounding this alert', + } +); + export const reasonActionVariableDescription = i18n.translate( 'xpack.infra.metrics.alerting.reasonActionVariableDescription', { @@ -211,7 +219,7 @@ export const viewInAppUrlActionVariableDescription = i18n.translate( 'xpack.infra.metrics.alerting.viewInAppUrlActionVariableDescription', { defaultMessage: - 'Link to the view or feature within Elastic that can be used to investigate the alert and its context further', + 'Link to the view or feature within Elastic that can assist with further investigation', } ); diff --git a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts index 2618af72168cd..ced80c75a3ef1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts @@ -9,8 +9,11 @@ import { isEmpty, isError } from 'lodash'; import { schema } from '@kbn/config-schema'; import { Logger, LogMeta } from '@kbn/logging'; import type { IBasePath } from '@kbn/core/server'; +import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; +import { ObservabilityConfig } from '@kbn/observability-plugin/server'; import { ALERT_RULE_PARAMETERS, TIMESTAMP } from '@kbn/rule-data-utils'; import { parseTechnicalFields } from '@kbn/rule-registry-plugin/common/parse_technical_fields'; +import { LINK_TO_METRICS_EXPLORER } from '../../../../common/alerting/metrics'; import { getInventoryViewInAppUrl } from '../../../../common/alerting/metrics/alert_link'; import { AlertExecutionDetails, @@ -83,18 +86,30 @@ export const createScopedLogger = ( }; }; -export const getViewInAppUrl = (basePath: IBasePath, relativeViewInAppUrl: string) => - basePath.publicBaseUrl - ? new URL(basePath.prepend(relativeViewInAppUrl), basePath.publicBaseUrl).toString() - : relativeViewInAppUrl; +export const getAlertDetailsPageEnabledForApp = ( + config: ObservabilityConfig['unsafe']['alertDetails'] | null, + appName: keyof ObservabilityConfig['unsafe']['alertDetails'] +): boolean => { + if (!config) return false; -export const getViewInAppUrlInventory = ( - criteria: InventoryMetricConditions[], - nodeType: string, - timestamp: string, - basePath: IBasePath -) => { + return config[appName].enabled; +}; + +export const getViewInInventoryAppUrl = ({ + basePath, + criteria, + nodeType, + spaceId, + timestamp, +}: { + basePath: IBasePath; + criteria: InventoryMetricConditions[]; + nodeType: string; + spaceId: string; + timestamp: string; +}) => { const { metric, customMetric } = criteria[0]; + const fields = { [`${ALERT_RULE_PARAMETERS}.criteria.metric`]: [metric], [`${ALERT_RULE_PARAMETERS}.criteria.customMetric.id`]: [customMetric?.id], @@ -104,6 +119,18 @@ export const getViewInAppUrlInventory = ( [TIMESTAMP]: timestamp, }; - const relativeViewInAppUrl = getInventoryViewInAppUrl(parseTechnicalFields(fields, true)); - return getViewInAppUrl(basePath, relativeViewInAppUrl); + return addSpaceIdToPath( + basePath.publicBaseUrl, + spaceId, + getInventoryViewInAppUrl(parseTechnicalFields(fields, true)) + ); }; + +export const getViewInMetricsAppUrl = (basePath: IBasePath, spaceId: string) => + addSpaceIdToPath(basePath.publicBaseUrl, spaceId, LINK_TO_METRICS_EXPLORER); + +export const getAlertDetailsUrl = ( + basePath: IBasePath, + spaceId: string, + alertUuid: string | null +) => addSpaceIdToPath(basePath.publicBaseUrl, spaceId, `/app/observability/alerts/${alertUuid}`); diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 2e51d2e8291b5..20b804a2cc7db 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -30,7 +30,12 @@ import { buildNoDataAlertReason, stateToAlertMessage, } from '../common/messages'; -import { createScopedLogger, getViewInAppUrlInventory } from '../common/utils'; +import { + createScopedLogger, + getAlertDetailsUrl, + getViewInInventoryAppUrl, + UNGROUPED_FACTORY_KEY, +} from '../common/utils'; import { evaluateCondition, ConditionResult } from './evaluate_condition'; type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf< @@ -61,12 +66,18 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = InventoryMetricThresholdAlertState, InventoryMetricThresholdAlertContext, InventoryMetricThresholdAllowedActionGroups - >(async ({ services, params, alertId, executionId, startedAt }) => { + >(async ({ services, params, alertId, executionId, spaceId, startedAt }) => { const startTime = Date.now(); + const { criteria, filterQuery, sourceId = 'default', nodeType, alertOnNoData } = params; + if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); + const logger = createScopedLogger(libs.logger, 'inventoryRule', { alertId, executionId }); - const { alertWithLifecycle, savedObjectsClient, getAlertStartedDate } = services; + + const esClient = services.scopedClusterClient.asCurrentUser; + + const { alertWithLifecycle, savedObjectsClient, getAlertStartedDate, getAlertUuid } = services; const alertFactory: InventoryMetricThresholdAlertFactory = (id, reason, additionalContext) => alertWithLifecycle({ id, @@ -85,23 +96,28 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = logger.error(e.message); const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able const reason = buildInvalidQueryAlertReason(params.filterQueryText); - const alert = alertFactory('*', reason); - const indexedStartedDate = getAlertStartedDate('*') ?? startedAt.toISOString(); - const viewInAppUrl = getViewInAppUrlInventory( - criteria, - nodeType, - indexedStartedDate, - libs.basePath - ); + const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason); + const indexedStartedDate = + getAlertStartedDate(UNGROUPED_FACTORY_KEY) ?? startedAt.toISOString(); + const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); + alert.scheduleActions(actionGroupId, { - group: '*', + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[AlertStates.ERROR], + group: UNGROUPED_FACTORY_KEY, + metric: mapToConditionsLookup(criteria, (c) => c.metric), reason, timestamp: startedAt.toISOString(), - viewInAppUrl, value: null, - metric: mapToConditionsLookup(criteria, (c) => c.metric), + viewInAppUrl: getViewInInventoryAppUrl({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedDate, + spaceId, + }), }); + return {}; } } @@ -109,7 +125,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const [, , { logViews }] = await libs.getStartServices(); const logQueryFields: LogQueryFields | undefined = await logViews - .getClient(savedObjectsClient, services.scopedClusterClient.asCurrentUser) + .getClient(savedObjectsClient, esClient) .getResolvedLogView(sourceId) .then( ({ indices }) => ({ indexPattern: indices }), @@ -120,18 +136,19 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const results = await Promise.all( criteria.map((condition) => evaluateCondition({ - condition, - nodeType, - source, - logQueryFields, - esClient: services.scopedClusterClient.asCurrentUser, compositeSize, - filterQuery, + condition, + esClient, executionTimestamp: startedAt, + filterQuery, logger, + logQueryFields, + nodeType, + source, }) ) ); + let scheduledActionsCount = 0; const inventoryItems = Object.keys(first(results)!); for (const group of inventoryItems) { @@ -190,25 +207,28 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const alert = alertFactory(group, reason, additionalContext); const indexedStartedDate = getAlertStartedDate(group) ?? startedAt.toISOString(); - const viewInAppUrl = getViewInAppUrlInventory( - criteria, - nodeType, - indexedStartedDate, - libs.basePath - ); + const alertUuid = getAlertUuid(group); + scheduledActionsCount++; const context = { - group, + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[nextState], + group, reason, + metric: mapToConditionsLookup(criteria, (c) => c.metric), timestamp: startedAt.toISOString(), - viewInAppUrl, + threshold: mapToConditionsLookup(criteria, (c) => c.threshold), value: mapToConditionsLookup(results, (result) => formatMetric(result[group].metric, result[group].currentValue) ), - threshold: mapToConditionsLookup(criteria, (c) => c.threshold), - metric: mapToConditionsLookup(criteria, (c) => c.metric), + viewInAppUrl: getViewInInventoryAppUrl({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedDate, + spaceId, + }), ...additionalContext, }; alert.scheduleActions(actionGroupId, context); @@ -217,24 +237,27 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const { getRecoveredAlerts } = services.alertFactory.done(); const recoveredAlerts = getRecoveredAlerts(); + for (const alert of recoveredAlerts) { const recoveredAlertId = alert.getId(); const indexedStartedDate = getAlertStartedDate(recoveredAlertId) ?? startedAt.toISOString(); - const viewInAppUrl = getViewInAppUrlInventory( - criteria, - nodeType, - indexedStartedDate, - libs.basePath - ); - const context = { - group: recoveredAlertId, + const alertUuid = getAlertUuid(recoveredAlertId); + + alert.setContext({ + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[AlertStates.OK], - timestamp: startedAt.toISOString(), - viewInAppUrl, - threshold: mapToConditionsLookup(criteria, (c) => c.threshold), + group: recoveredAlertId, metric: mapToConditionsLookup(criteria, (c) => c.metric), - }; - alert.setContext(context); + threshold: mapToConditionsLookup(criteria, (c) => c.threshold), + timestamp: startedAt.toISOString(), + viewInAppUrl: getViewInInventoryAppUrl({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedDate, + spaceId, + }), + }); } const stopTime = Date.now(); diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts index b3b1f5ba21c65..030628f59ad38 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts @@ -24,6 +24,7 @@ import { } from '../../../../common/inventory_models/types'; import { InfraBackendLibs } from '../../infra_types'; import { + alertDetailUrlActionVariableDescription, alertStateActionVariableDescription, cloudActionVariableDescription, containerActionVariableDescription, @@ -39,7 +40,11 @@ import { valueActionVariableDescription, viewInAppUrlActionVariableDescription, } from '../common/messages'; -import { oneOfLiterals, validateIsStringElasticsearchJSONFilter } from '../common/utils'; +import { + getAlertDetailsPageEnabledForApp, + oneOfLiterals, + validateIsStringElasticsearchJSONFilter, +} from '../common/utils'; import { createInventoryMetricThresholdExecutor, FIRED_ACTIONS, @@ -72,6 +77,8 @@ export async function registerMetricInventoryThresholdRuleType( alertingPlugin: PluginSetupContract, libs: InfraBackendLibs ) { + const config = libs.getAlertDetailsConfig(); + alertingPlugin.registerType({ id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, name: i18n.translate('xpack.infra.metrics.inventory.alertName', { @@ -102,6 +109,9 @@ export async function registerMetricInventoryThresholdRuleType( context: [ { name: 'group', description: groupActionVariableDescription }, { name: 'alertState', description: alertStateActionVariableDescription }, + ...(getAlertDetailsPageEnabledForApp(config, 'metrics') + ? [{ name: 'alertDetailsUrl', description: alertDetailUrlActionVariableDescription }] + : []), { name: 'reason', description: reasonActionVariableDescription }, { name: 'timestamp', description: timestampActionVariableDescription }, { name: 'value', description: valueActionVariableDescription }, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 14b5fe8e75614..200ad68aa81d1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -26,8 +26,12 @@ import { // buildRecoveredAlertReason, stateToAlertMessage, } from '../common/messages'; -import { UNGROUPED_FACTORY_KEY, getViewInAppUrl, createScopedLogger } from '../common/utils'; -import { LINK_TO_METRICS_EXPLORER } from '../../../../common/alerting/metrics'; +import { + createScopedLogger, + getAlertDetailsUrl, + getViewInMetricsAppUrl, + UNGROUPED_FACTORY_KEY, +} from '../common/utils'; import { EvaluatedRuleParams, evaluateRule } from './lib/evaluate_rule'; @@ -67,11 +71,16 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => MetricThresholdAllowedActionGroups >(async function (options) { const startTime = Date.now(); - const { services, params, state, startedAt, alertId, executionId } = options; + + const { services, params, state, startedAt, alertId, executionId, spaceId } = options; + const { criteria } = params; if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); + const logger = createScopedLogger(libs.logger, 'metricThresholdRule', { alertId, executionId }); - const { alertWithLifecycle, savedObjectsClient } = services; + + const { alertWithLifecycle, savedObjectsClient, getAlertUuid } = services; + const alertFactory: MetricThresholdAlertFactory = (id, reason) => alertWithLifecycle({ id, @@ -100,15 +109,19 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able const reason = buildInvalidQueryAlertReason(params.filterQueryText); const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason); + const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); + alert.scheduleActions(actionGroupId, { - group: UNGROUPED_FACTORY_KEY, + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[AlertStates.ERROR], + group: UNGROUPED_FACTORY_KEY, + metric: mapToConditionsLookup(criteria, (c) => c.metric), reason, - viewInAppUrl: getViewInAppUrl(libs.basePath, LINK_TO_METRICS_EXPLORER), timestamp, value: null, - metric: mapToConditionsLookup(criteria, (c) => c.metric), + viewInAppUrl: getViewInMetricsAppUrl(libs.basePath, spaceId), }); + return { lastRunTimestamp: startedAt.valueOf(), missingGroups: [], @@ -157,6 +170,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const hasGroups = !isEqual(groups, [UNGROUPED_FACTORY_KEY]); let scheduledActionsCount = 0; + // The key of `groups` is the alert instance ID. for (const group of groups) { // AND logic; all criteria must be across the threshold const shouldAlertFire = alertResults.every((result) => result[group]?.shouldFire); @@ -227,40 +241,45 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => ? WARNING_ACTIONS.id : FIRED_ACTIONS.id; const alert = alertFactory(`${group}`, reason); + const alertUuid = getAlertUuid(group); scheduledActionsCount++; + alert.scheduleActions(actionGroupId, { - group, + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[nextState], + group, + metric: mapToConditionsLookup(criteria, (c) => c.metric), reason, - viewInAppUrl: getViewInAppUrl(libs.basePath, LINK_TO_METRICS_EXPLORER), + threshold: mapToConditionsLookup( + alertResults, + (result) => formatAlertResult(result[group]).threshold + ), timestamp, value: mapToConditionsLookup( alertResults, (result) => formatAlertResult(result[group]).currentValue ), - threshold: mapToConditionsLookup( - alertResults, - (result) => formatAlertResult(result[group]).threshold - ), - metric: mapToConditionsLookup(criteria, (c) => c.metric), + viewInAppUrl: getViewInMetricsAppUrl(libs.basePath, spaceId), }); } } const { getRecoveredAlerts } = services.alertFactory.done(); const recoveredAlerts = getRecoveredAlerts(); + for (const alert of recoveredAlerts) { const recoveredAlertId = alert.getId(); - const viewInAppUrl = getViewInAppUrl(libs.basePath, LINK_TO_METRICS_EXPLORER); - const context = { - group: recoveredAlertId, + const alertUuid = getAlertUuid(recoveredAlertId); + + alert.setContext({ + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[AlertStates.OK], + group: recoveredAlertId, + metric: mapToConditionsLookup(criteria, (c) => c.metric), timestamp: startedAt.toISOString(), - viewInAppUrl, threshold: mapToConditionsLookup(criteria, (c) => c.threshold), - metric: mapToConditionsLookup(criteria, (c) => c.metric), - }; - alert.setContext(context); + viewInAppUrl: getViewInMetricsAppUrl(libs.basePath, spaceId), + }); } const stopTime = Date.now(); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts index 0ebb427819f74..6538fb25b6c8c 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts @@ -13,6 +13,7 @@ import { Comparator, METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/a import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api'; import { InfraBackendLibs } from '../../infra_types'; import { + alertDetailUrlActionVariableDescription, alertStateActionVariableDescription, groupActionVariableDescription, metricActionVariableDescription, @@ -22,7 +23,11 @@ import { valueActionVariableDescription, viewInAppUrlActionVariableDescription, } from '../common/messages'; -import { oneOfLiterals, validateIsStringElasticsearchJSONFilter } from '../common/utils'; +import { + getAlertDetailsPageEnabledForApp, + oneOfLiterals, + validateIsStringElasticsearchJSONFilter, +} from '../common/utils'; import { createMetricThresholdExecutor, FIRED_ACTIONS, @@ -41,6 +46,8 @@ export async function registerMetricThresholdRuleType( alertingPlugin: PluginSetupContract, libs: InfraBackendLibs ) { + const config = libs.getAlertDetailsConfig(); + const baseCriterion = { threshold: schema.arrayOf(schema.number()), comparator: oneOfLiterals(Object.values(Comparator)), @@ -93,6 +100,9 @@ export async function registerMetricThresholdRuleType( actionVariables: { context: [ { name: 'group', description: groupActionVariableDescription }, + ...(getAlertDetailsPageEnabledForApp(config, 'metrics') + ? [{ name: 'alertDetailsUrl', description: alertDetailUrlActionVariableDescription }] + : []), { name: 'alertState', description: alertStateActionVariableDescription }, { name: 'reason', description: reasonActionVariableDescription }, { name: 'timestamp', description: timestampActionVariableDescription }, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts index 37e59700b0488..3d3c7a17cd1dd 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts @@ -4,10 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import * as utils from '../common/utils'; -jest - .spyOn(utils, 'getViewInAppUrl') - .mockReturnValue('http://localhost:5601/eyg/app/metrics/explorer'); const bucketsA = (from: number) => [ { diff --git a/x-pack/plugins/infra/server/lib/infra_types.ts b/x-pack/plugins/infra/server/lib/infra_types.ts index a4636daa7986e..4801fb49651f6 100644 --- a/x-pack/plugins/infra/server/lib/infra_types.ts +++ b/x-pack/plugins/infra/server/lib/infra_types.ts @@ -8,6 +8,7 @@ import { Logger } from '@kbn/logging'; import type { IBasePath } from '@kbn/core/server'; import { handleEsError } from '@kbn/es-ui-shared-plugin/server'; +import { ObservabilityConfig } from '@kbn/observability-plugin/server'; import { RulesServiceSetup } from '../services/rules'; import { InfraConfig, InfraPluginStartServicesAccessor } from '../types'; import { KibanaFramework } from './adapters/framework/kibana_framework_adapter'; @@ -24,14 +25,15 @@ export interface InfraDomainLibs { } export interface InfraBackendLibs extends InfraDomainLibs { + basePath: IBasePath; configuration: InfraConfig; framework: KibanaFramework; - sources: InfraSources; - sourceStatus: InfraSourceStatus; - handleEsError: typeof handleEsError; logsRules: RulesServiceSetup; metricsRules: RulesServiceSetup; + sources: InfraSources; + sourceStatus: InfraSourceStatus; + getAlertDetailsConfig: () => ObservabilityConfig['unsafe']['alertDetails']; getStartServices: InfraPluginStartServicesAccessor; + handleEsError: typeof handleEsError; logger: Logger; - basePath: IBasePath; } diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index bfd7113ec4dc0..a7fa9ceacd3c9 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -170,6 +170,7 @@ export class InfraServerPlugin logsRules: this.logsRules.setup(core, plugins), metricsRules: this.metricsRules.setup(core, plugins), getStartServices: () => core.getStartServices(), + getAlertDetailsConfig: () => plugins.observability.getAlertDetailsConfig(), logger: this.logger, basePath: core.http.basePath, }; diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts index 2931555fc06b0..07fa1ac9cd1cf 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/apply_metadata_to_last_path.ts @@ -46,7 +46,7 @@ export const applyMetadataToLastPath = ( // Set the label as the name and fallback to the id OR path.value lastPath.label = (firstMetaDoc[inventoryFields.name] ?? lastPath.value) as string; // If the inventory fields contain an ip address, we need to try and set that - // on the path object. IP addersses are typically stored as multiple fields. We will + // on the path object. IP addresses are typically stored as multiple fields. We will // use the first IPV4 address we find. if (inventoryFields.ip) { const ipAddresses = get(firstMetaDoc, inventoryFields.ip) as string[]; @@ -56,6 +56,9 @@ export const applyMetadataToLastPath = ( lastPath.ip = ipAddresses; } } + if (inventoryFields.os) { + lastPath.os = get(firstMetaDoc, inventoryFields.os) as string; + } return [...node.path.slice(0, node.path.length - 1), lastPath]; } } diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts index b7e389cae9126..87fbbdac9a701 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/transform_request_to_metrics_api_request.ts @@ -93,6 +93,11 @@ export const transformRequestToMetricsAPIRequest = async ({ if (inventoryFields.ip) { metaAggregation.aggregations[META_KEY].top_metrics.metrics.push({ field: inventoryFields.ip }); } + + if (inventoryFields.os) { + metaAggregation.aggregations[META_KEY].top_metrics.metrics.push({ field: inventoryFields.os }); + } + metricsApiRequest.metrics.push(metaAggregation); if (filters.length) { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx index c5daa1db2ac07..9e31c71e0f275 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx @@ -39,7 +39,7 @@ describe('Pipeline Editor', () => { let testBed: SetupResult; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/append.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/append.test.tsx index f35e05b800f14..632dc824dbd1a 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/append.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/append.test.tsx @@ -17,7 +17,7 @@ describe('Processor: Append', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx index d4ac176d6aaf5..44acef642caa5 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/bytes.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Bytes', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx index 00153471ea65e..01b25bc508bcc 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/circle.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Circle', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx index ebffd5adf78c1..45781e15eca59 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/common_processor_fields.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Common Fields For All Processors', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/community_id.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/community_id.test.tsx index e571474576ff2..4ed8ff5b05285 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/community_id.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/community_id.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Community id', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx index 3090e59b32e0b..d328bb6759b2f 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/convert.test.tsx @@ -26,7 +26,7 @@ describe('Processor: Convert', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/csv.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/csv.test.tsx index 6414976b56f9a..c3237c1d23259 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/csv.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/csv.test.tsx @@ -29,7 +29,7 @@ describe('Processor: CSV', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx index 22666ebbe2a98..fa1edd6d6cb51 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Date', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date_index.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date_index.test.tsx index b9e990f36c15b..74a49b11821ee 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date_index.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/date_index.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Date Index Name', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/dot_expander.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/dot_expander.test.tsx index 7ebad2de01a92..c95a254870418 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/dot_expander.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/dot_expander.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Dot Expander', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fail.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fail.test.tsx index 9b8148bd0dffd..94a7bc65e65cc 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fail.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fail.test.tsx @@ -15,7 +15,7 @@ describe('Processor: Fail', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fingerprint.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fingerprint.test.tsx index 49d7937fab002..b5b5fa9c9e1d3 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fingerprint.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/fingerprint.test.tsx @@ -28,7 +28,7 @@ describe('Processor: Fingerprint', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/grok.test.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/grok.test.ts index 90ea4ed1a0105..89efc203c0c53 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/grok.test.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/grok.test.ts @@ -17,7 +17,7 @@ describe('Processor: Grok', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // disable all react-beautiful-dnd development warnings (window as any)['__react-beautiful-dnd-disable-dev-warnings'] = true; }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/network_direction.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/network_direction.test.tsx index 4f92aec06efd3..330b651b5b515 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/network_direction.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/network_direction.test.tsx @@ -30,7 +30,7 @@ describe('Processor: Network Direction', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor_form.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor_form.test.tsx index b2e2fb81f2c86..1b89dbe835a96 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor_form.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor_form.test.tsx @@ -14,7 +14,7 @@ describe('Processor: Bytes', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/registered_domain.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/registered_domain.test.tsx index dcf332912a94b..b4fafe6a47572 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/registered_domain.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/registered_domain.test.tsx @@ -24,7 +24,7 @@ describe('Processor: Registered Domain', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx index ebfa678648904..d90c34ee10c8a 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/set.test.tsx @@ -16,7 +16,7 @@ describe('Processor: Set', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx index 9062fcc02f7f8..e59dba0aecddf 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/uri_parts.test.tsx @@ -22,7 +22,7 @@ describe('Processor: URI parts', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx index ed778aa1cc1f3..e2e083715c88e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/user_agent.test.tsx @@ -27,7 +27,7 @@ describe('Processor: User Agent', () => { const { httpSetup } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx index b15172185cff2..5bd7d1d30aea5 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx @@ -46,7 +46,7 @@ describe('Test pipeline', () => { }; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/field_item.tsx b/x-pack/plugins/lens/public/datasources/form_based/field_item.tsx index 5ebfecc4cc95a..e2ee0559b3808 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/field_item.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/field_item.tsx @@ -190,6 +190,9 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { initialFocus=".lnsFieldItem__fieldPanel" className="lnsFieldItem__popoverAnchor" data-test-subj="lnsFieldListPanelField" + panelProps={{ + 'data-test-subj': 'lnsFieldListPanelFieldContent', + }} container={document.querySelector<HTMLElement>('.application') || undefined} button={ <DragDrop @@ -331,26 +334,30 @@ function FieldItemPopoverContents( field={dataViewField} data-test-subj="lnsFieldListPanel" overrideMissingContent={(params) => { - if (params?.noDataFound) { + if (params.reason === 'no-data') { // TODO: should we replace this with a default message "Analysis is not available for this field?" const isUsingSampling = core.uiSettings.get('lens:useFieldExistenceSampling'); return ( - <> - <EuiText size="s"> - {isUsingSampling - ? i18n.translate('xpack.lens.indexPattern.fieldStatsSamplingNoData', { - defaultMessage: - 'Lens is unable to create visualizations with this field because it does not contain data in the first 500 documents that match your filters. To create a visualization, drag and drop a different field.', - }) - : i18n.translate('xpack.lens.indexPattern.fieldStatsNoData', { - defaultMessage: - 'Lens is unable to create visualizations with this field because it does not contain data. To create a visualization, drag and drop a different field.', - })} - </EuiText> - </> + <EuiText size="s" data-test-subj="lnsFieldListPanel-missingFieldStats"> + {isUsingSampling + ? i18n.translate('xpack.lens.indexPattern.fieldStatsSamplingNoData', { + defaultMessage: + 'Lens is unable to create visualizations with this field because it does not contain data in the first 500 documents that match your filters. To create a visualization, drag and drop a different field.', + }) + : i18n.translate('xpack.lens.indexPattern.fieldStatsNoData', { + defaultMessage: + 'Lens is unable to create visualizations with this field because it does not contain data. To create a visualization, drag and drop a different field.', + })} + </EuiText> + ); + } + if (params.reason === 'unsupported') { + return ( + <EuiText data-test-subj="lnsFieldListPanel-missingFieldStats"> + {params.element} + </EuiText> ); } - return params.element; }} /> diff --git a/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.tsx b/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.tsx index 105c9583e300d..d6b4c73b51082 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.tsx @@ -169,14 +169,18 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ } if (hasLoaded) { return ( - <EuiNotificationBadge size="m" color={isFiltered ? 'accent' : 'subdued'}> + <EuiNotificationBadge + size="m" + color={isFiltered ? 'accent' : 'subdued'} + data-test-subj={`${id}-count`} + > {fieldsCount} </EuiNotificationBadge> ); } return <EuiLoadingSpinner size="m" />; - }, [showExistenceFetchError, showExistenceFetchTimeout, hasLoaded, isFiltered, fieldsCount]); + }, [showExistenceFetchError, showExistenceFetchTimeout, hasLoaded, isFiltered, id, fieldsCount]); return ( <EuiAccordion diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts index e8ac68460beb9..fdaf1f51c644b 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.test.ts @@ -315,7 +315,9 @@ describe('IndexPattern Data Source', () => { describe('#toExpression', () => { it('should generate an empty expression when no columns are selected', async () => { const state = FormBasedDatasource.initialize(); - expect(FormBasedDatasource.toExpression(state, 'first', indexPatterns)).toEqual(null); + expect( + FormBasedDatasource.toExpression(state, 'first', indexPatterns, 'testing-seed') + ).toEqual(null); }); it('should create a table when there is a formula without aggs', async () => { @@ -338,7 +340,9 @@ describe('IndexPattern Data Source', () => { }, }, }; - expect(FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns)).toEqual({ + expect( + FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns, 'testing-seed') + ).toEqual({ chain: [ { function: 'createTable', @@ -385,8 +389,9 @@ describe('IndexPattern Data Source', () => { }, }; - expect(FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns)) - .toMatchInlineSnapshot(` + expect( + FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns, 'testing-seed') + ).toMatchInlineSnapshot(` Object { "chain": Array [ Object { @@ -487,6 +492,12 @@ describe('IndexPattern Data Source', () => { "partialRows": Array [ false, ], + "probability": Array [ + 1, + ], + "samplerSeed": Array [ + 1889181588, + ], "timeFields": Array [ "timestamp", ], @@ -560,7 +571,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(ast.chain[1].arguments.timeFields).toEqual(['timestamp', 'another_datefield']); }); @@ -595,7 +611,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect((ast.chain[1].arguments.aggs[1] as Ast).chain[0].arguments.timeShift).toEqual(['1d']); }); @@ -802,7 +823,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const count = (ast.chain[1].arguments.aggs[1] as Ast).chain[0]; const sum = (ast.chain[1].arguments.aggs[2] as Ast).chain[0]; const average = (ast.chain[1].arguments.aggs[3] as Ast).chain[0]; @@ -866,7 +892,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(ast.chain[1].arguments.aggs[0]).toMatchInlineSnapshot(` Object { "chain": Array [ @@ -990,7 +1021,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const timeScaleCalls = ast.chain.filter((fn) => fn.function === 'lens_time_scale'); const formatCalls = ast.chain.filter((fn) => fn.function === 'lens_format_column'); expect(timeScaleCalls).toHaveLength(1); @@ -1055,7 +1091,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const filteredMetricAgg = (ast.chain[1].arguments.aggs[0] as Ast).chain[0].arguments; const metricAgg = (filteredMetricAgg.customMetric[0] as Ast).chain[0].arguments; const bucketAgg = (filteredMetricAgg.customBucket[0] as Ast).chain[0].arguments; @@ -1106,7 +1147,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const formatIndex = ast.chain.findIndex((fn) => fn.function === 'lens_format_column'); const calculationIndex = ast.chain.findIndex((fn) => fn.function === 'moving_average'); expect(calculationIndex).toBeLessThan(formatIndex); @@ -1154,7 +1200,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(ast.chain[1].arguments.metricsAtAllLevels).toEqual([false]); expect(JSON.parse(ast.chain[2].arguments.idMap[0] as string)).toEqual({ 'col-0-0': [expect.objectContaining({ id: 'bucket1' })], @@ -1193,7 +1244,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(ast.chain[1].arguments.timeFields).toEqual(['timestamp']); expect(ast.chain[1].arguments.timeFields).not.toContain('timefield'); }); @@ -1250,7 +1306,7 @@ describe('IndexPattern Data Source', () => { const optimizeMock = jest.spyOn(operationDefinitionMap.percentile, 'optimizeEsAggs'); - FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns); + FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns, 'testing-seed'); expect(operationDefinitionMap.percentile.optimizeEsAggs).toHaveBeenCalledTimes(1); @@ -1318,7 +1374,12 @@ describe('IndexPattern Data Source', () => { return { aggs: aggs.reverse(), esAggsIdMap }; }); - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(operationDefinitionMap.percentile.optimizeEsAggs).toHaveBeenCalledTimes(1); @@ -1382,7 +1443,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const idMap = JSON.parse(ast.chain[2].arguments.idMap as unknown as string); @@ -1487,7 +1553,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; // @ts-expect-error we can't isolate just the reference type expect(operationDefinitionMap.testReference.toExpression).toHaveBeenCalled(); expect(ast.chain[3]).toEqual('mock'); @@ -1520,7 +1591,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; expect(JSON.parse(ast.chain[2].arguments.idMap[0] as string)).toEqual({ 'col-0-0': [ @@ -1607,7 +1683,12 @@ describe('IndexPattern Data Source', () => { }, }; - const ast = FormBasedDatasource.toExpression(queryBaseState, 'first', indexPatterns) as Ast; + const ast = FormBasedDatasource.toExpression( + queryBaseState, + 'first', + indexPatterns, + 'testing-seed' + ) as Ast; const chainLength = ast.chain.length; expect(ast.chain[chainLength - 2].arguments.name).toEqual(['math']); expect(ast.chain[chainLength - 1].arguments.id).toEqual(['formula']); @@ -1631,6 +1712,7 @@ describe('IndexPattern Data Source', () => { }, }, currentIndexPatternId: '1', + sampling: 1, }; expect(FormBasedDatasource.insertLayer(state, 'newLayer', ['link-to-id'])).toEqual({ ...state, @@ -1640,6 +1722,7 @@ describe('IndexPattern Data Source', () => { indexPatternId: '1', columnOrder: [], columns: {}, + sampling: 1, linkToLayers: ['link-to-id'], }, }, @@ -1665,12 +1748,103 @@ describe('IndexPattern Data Source', () => { currentIndexPatternId: '1', }; expect(FormBasedDatasource.removeLayer(state, 'first')).toEqual({ - ...state, + removedLayerIds: ['first'], + newState: { + ...state, + layers: { + second: { + indexPatternId: '2', + columnOrder: [], + columns: {}, + }, + }, + }, + }); + }); + + it('should remove linked layers', () => { + const state = { layers: { + first: { + indexPatternId: '1', + columnOrder: [], + columns: {}, + }, second: { indexPatternId: '2', columnOrder: [], columns: {}, + linkToLayers: ['first'], + }, + }, + currentIndexPatternId: '1', + }; + expect(FormBasedDatasource.removeLayer(state, 'first')).toEqual({ + removedLayerIds: ['first', 'second'], + newState: { + ...state, + layers: {}, + }, + }); + }); + }); + + describe('#clearLayer', () => { + it('should clear a layer', () => { + const state = { + layers: { + first: { + indexPatternId: '1', + columnOrder: ['some', 'order'], + columns: { + some: {} as GenericIndexPatternColumn, + columns: {} as GenericIndexPatternColumn, + }, + linkToLayers: ['some-layer'], + }, + }, + currentIndexPatternId: '1', + }; + expect(FormBasedDatasource.clearLayer(state, 'first')).toEqual({ + removedLayerIds: [], + newState: { + ...state, + layers: { + first: { + indexPatternId: '1', + columnOrder: [], + columns: {}, + linkToLayers: ['some-layer'], + sampling: 1, + }, + }, + }, + }); + }); + + it('should remove linked layers', () => { + const state = { + layers: { + first: { + indexPatternId: '1', + columnOrder: [], + columns: {}, + }, + second: { + indexPatternId: '2', + columnOrder: [], + columns: {}, + linkToLayers: ['first'], + }, + }, + currentIndexPatternId: '1', + }; + expect(FormBasedDatasource.clearLayer(state, 'first')).toEqual({ + removedLayerIds: ['second'], + newState: { + ...state, + layers: { + first: { ...state.layers.first, linkToLayers: undefined, sampling: 1 }, }, }, }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx index d41218f785d9b..b863c69d7f7a6 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx @@ -36,6 +36,7 @@ import type { IndexPatternField, IndexPattern, IndexPatternRef, + DatasourceLayerSettingsProps, } from '../../types'; import { changeIndexPattern, @@ -96,6 +97,7 @@ import { getStateTimeShiftWarningMessages } from './time_shift_utils'; import { getPrecisionErrorWarningMessages } from './utils'; import { DOCUMENT_FIELD_NAME } from '../../../common/constants'; import { isColumnOfType } from './operations/definitions/helpers'; +import { LayerSettingsPanel } from './layer_settings'; import { FormBasedLayer } from '../..'; export type { OperationType, GenericIndexPatternColumn } from './operations'; export { deleteColumn } from './operations'; @@ -218,19 +220,47 @@ export function getFormBasedDatasource({ removeLayer(state: FormBasedPrivateState, layerId: string) { const newLayers = { ...state.layers }; delete newLayers[layerId]; + const removedLayerIds: string[] = [layerId]; + + // delete layers linked to this layer + Object.keys(newLayers).forEach((id) => { + const linkedLayers = newLayers[id]?.linkToLayers; + if (linkedLayers && linkedLayers.includes(layerId)) { + delete newLayers[id]; + removedLayerIds.push(id); + } + }); return { - ...state, - layers: newLayers, + removedLayerIds, + newState: { + ...state, + layers: newLayers, + }, }; }, clearLayer(state: FormBasedPrivateState, layerId: string) { + const newLayers = { ...state.layers }; + + const removedLayerIds: string[] = []; + // delete layers linked to this layer + Object.keys(newLayers).forEach((id) => { + const linkedLayers = newLayers[id]?.linkToLayers; + if (linkedLayers && linkedLayers.includes(layerId)) { + delete newLayers[id]; + removedLayerIds.push(id); + } + }); + return { - ...state, - layers: { - ...state.layers, - [layerId]: blankLayer(state.currentIndexPatternId, state.layers[layerId].linkToLayers), + removedLayerIds, + newState: { + ...state, + layers: { + ...newLayers, + [layerId]: blankLayer(state.currentIndexPatternId, state.layers[layerId].linkToLayers), + }, }, }; }, @@ -384,8 +414,34 @@ export function getFormBasedDatasource({ return fields; }, - toExpression: (state, layerId, indexPatterns) => - toExpression(state, layerId, indexPatterns, uiSettings), + toExpression: (state, layerId, indexPatterns, searchSessionId) => + toExpression(state, layerId, indexPatterns, uiSettings, searchSessionId), + + renderLayerSettings( + domElement: Element, + props: DatasourceLayerSettingsProps<FormBasedPrivateState> + ) { + render( + <KibanaThemeProvider theme$={core.theme.theme$}> + <I18nProvider> + <KibanaContextProvider + services={{ + ...core, + data, + dataViews, + fieldFormats, + charts, + unifiedSearch, + discover, + }} + > + <LayerSettingsPanel {...props} /> + </KibanaContextProvider> + </I18nProvider> + </KibanaThemeProvider>, + domElement + ); + }, renderDataPanel(domElement: Element, props: DatasourceDataPanelProps<FormBasedPrivateState>) { const { onChangeIndexPattern, ...otherProps } = props; @@ -561,6 +617,22 @@ export function getFormBasedDatasource({ getDropProps, onDrop, + getSupportedActionsForLayer(layerId, state, _, openLayerSettings) { + if (!openLayerSettings) { + return []; + } + return [ + { + displayName: i18n.translate('xpack.lens.indexPattern.layerSettingsAction', { + defaultMessage: 'Layer settings', + }), + execute: openLayerSettings, + icon: 'gear', + isCompatible: Boolean(state.layers[layerId]), + 'data-test-subj': 'lnsLayerSettings', + }, + ]; + }, getCustomWorkspaceRenderer: ( state: FormBasedPrivateState, @@ -923,5 +995,6 @@ function blankLayer(indexPatternId: string, linkToLayers?: string[]): FormBasedL linkToLayers, columns: {}, columnOrder: [], + sampling: 1, }; } diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx index 2489659f0da55..eca0c032ee224 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.test.tsx @@ -176,6 +176,7 @@ function testInitialState(): FormBasedPrivateState { currentIndexPatternId: '1', layers: { first: { + sampling: 1, indexPatternId: '1', columnOrder: ['col1'], columns: { @@ -458,7 +459,7 @@ describe('IndexPattern Data Source suggestions', () => { }); describe('with a previous empty layer', () => { - function stateWithEmptyLayer() { + function stateWithEmptyLayer(): FormBasedPrivateState { const state = testInitialState(); return { ...state, @@ -761,6 +762,35 @@ describe('IndexPattern Data Source suggestions', () => { }) ); }); + + it('should inherit the sampling rate when generating new layer, if avaialble', () => { + const state = stateWithEmptyLayer(); + state.layers.previousLayer.sampling = 0.001; + const suggestions = getDatasourceSuggestionsForField( + state, + '1', + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + expectedIndexPatterns + ); + + expect(suggestions).toContainEqual( + expect.objectContaining({ + state: expect.objectContaining({ + layers: { + previousLayer: expect.objectContaining({ + sampling: 0.001, + }), + }, + }), + }) + ); + }); }); describe('suggesting extensions to non-empty tables', () => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts index 52008f10bcdeb..81ce81bb49053 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based_suggestions.ts @@ -529,6 +529,12 @@ function getEmptyLayerSuggestionsForField( newLayer = createNewLayerWithMetricAggregation(indexPattern, field); } + // copy the sampling rate to the new layer + // or just default to 1 + if (newLayer) { + newLayer.sampling = state.layers[layerId]?.sampling ?? 1; + } + const newLayerSuggestions = newLayer ? [ buildSuggestion({ diff --git a/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx b/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx new file mode 100644 index 0000000000000..ec161ef996737 --- /dev/null +++ b/x-pack/plugins/lens/public/datasources/form_based/layer_settings.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 { + EuiFormRow, + EuiRange, + EuiFlexGroup, + EuiFlexItem, + EuiBetaBadge, + EuiText, + EuiLink, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { DatasourceLayerSettingsProps } from '../../types'; +import type { FormBasedPrivateState } from './types'; + +const samplingValue = [0.0001, 0.001, 0.01, 0.1, 1]; + +export function LayerSettingsPanel({ + state, + setState, + layerId, +}: DatasourceLayerSettingsProps<FormBasedPrivateState>) { + const samplingIndex = samplingValue.findIndex((v) => v === state.layers[layerId].sampling); + const currentSamplingIndex = samplingIndex > -1 ? samplingIndex : samplingValue.length - 1; + return ( + <EuiFormRow + display="rowCompressed" + data-test-subj="lns-indexPattern-random-sampling-row" + fullWidth + helpText={ + <> + <EuiSpacer size="s" /> + <p> + <FormattedMessage + id="xpack.lens.xyChart.randomSampling.help" + defaultMessage="Lower sampling percentages increase speed at the cost of accuracy. It is recommended that lower sampling percentages only be used for large datasets. {link}" + values={{ + link: ( + <EuiLink + href="https://www.elastic.co/guide/en/elasticsearch/reference/master/search-aggregations-random-sampler-aggregation.html" + target="_blank" + external + > + <FormattedMessage + id="xpack.lens.xyChart.randomSampling.learnMore" + defaultMessage="View documentation" + /> + </EuiLink> + ), + }} + /> + </p> + </> + } + label={ + <> + {i18n.translate('xpack.lens.xyChart.randomSampling.label', { + defaultMessage: 'Random sampling', + })}{' '} + <EuiBetaBadge + css={css` + vertical-align: middle; + `} + iconType="beaker" + label={i18n.translate('xpack.lens.randomSampling.experimentalLabel', { + defaultMessage: 'Technical preview', + })} + size="s" + /> + </> + } + > + <EuiFlexGroup gutterSize="none"> + <EuiFlexItem grow={false}> + <EuiText color="subdued" size="xs"> + <FormattedMessage + id="xpack.lens.xyChart.randomSampling.speedLabel" + defaultMessage="Speed" + /> + </EuiText> + </EuiFlexItem> + <EuiFlexItem> + <EuiRange + data-test-subj="lns-indexPattern-random-sampling" + value={currentSamplingIndex} + onChange={(e) => { + setState({ + ...state, + layers: { + ...state.layers, + [layerId]: { + ...state.layers[layerId], + sampling: samplingValue[Number(e.currentTarget.value)], + }, + }, + }); + }} + showInput={false} + showRange={false} + showTicks + step={1} + min={0} + max={samplingValue.length - 1} + ticks={samplingValue.map((v, i) => ({ label: `${v * 100}%`, value: i }))} + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiText color="subdued" size="xs"> + <FormattedMessage + id="xpack.lens.xyChart.randomSampling.accuracyLabel" + defaultMessage="Accuracy" + /> + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFormRow> + ); +} diff --git a/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx b/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx index c18b79b28c58c..349139cd41b27 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx @@ -29,6 +29,13 @@ export function LayerPanel({ const notFoundTitleLabel = i18n.translate('xpack.lens.layerPanel.missingDataView', { defaultMessage: 'Data view not found', }); + const indexPatternRefs = dataViews.indexPatternRefs.map((ref) => { + const isPersisted = dataViews.indexPatterns[ref.id]?.isPersisted ?? true; + return { + ...ref, + isAdhoc: !isPersisted, + }; + }); return ( <I18nProvider> <ChangeIndexPattern @@ -41,7 +48,7 @@ export function LayerPanel({ fontWeight: 'normal', }} indexPatternId={layer.indexPatternId} - indexPatternRefs={dataViews.indexPatternRefs} + indexPatternRefs={indexPatternRefs} isMissingCurrent={!indexPattern} onChangeIndexPattern={onChangeIndexPattern} /> diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/count.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/count.tsx index 0d292b7a3a26e..60c1a0cdf0f5d 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/count.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/count.tsx @@ -235,7 +235,7 @@ export const countOperation: OperationDefinition<CountIndexPatternColumn, 'field }), description: i18n.translate('xpack.lens.indexPattern.count.documentation.markdown', { defaultMessage: ` -The total number of documents. When you provide a field as the first argument, the total number of field values is counted. Use the count function for fields that have multiple values in a single document. +The total number of documents. When you provide a field, the total number of field values is counted. When you use the Count function for fields that have multiple values in a single document, all values are counted. #### Examples @@ -249,7 +249,7 @@ To calculate the number of documents that match a specific filter, use \`count(k }, quickFunctionDocumentation: i18n.translate('xpack.lens.indexPattern.count.documentation.quick', { defaultMessage: ` -The total number of documents. When you provide a field, the total number of field values is counted. Use the count function for fields that have multiple values in a single document. +The total number of documents. When you provide a field, the total number of field values is counted. When you use the Count function for fields that have multiple values in a single document, all values are counted. `, }), shiftable: true, diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula.scss b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula.scss index 493f3525ec604..2b1753ebef1b3 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula.scss +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula.scss @@ -33,10 +33,6 @@ padding: $euiSizeS; } -.lnsFormula__docsHeader { - margin: 0; -} - .lnsFormula__editorFooter { // make sure docs are rendered in front of monaco z-index: 1; @@ -98,76 +94,6 @@ background: $euiColorEmptyShade; } -.lnsFormula__docs--inline { - display: flex; - flex-direction: column; - // make sure docs are rendered in front of monaco - z-index: 1; -} - -.lnsFormula__docsContent { - .lnsFormula__docs--overlay & { - height: 40vh; - width: #{'min(75vh, 90vw)'}; - } - - .lnsFormula__docs--inline & { - flex: 1; - min-height: 0; - } - - & > * + * { - border-left: $euiBorderThin; - } -} - -.lnsFormula__docsSidebar { - background: $euiColorLightestShade; -} - -.lnsFormula__docsSidebarInner { - min-height: 0; - - & > * + * { - border-top: $euiBorderThin; - } -} - -.lnsFormula__docsSearch { - padding: $euiSize; -} - -.lnsFormula__docsNav { - @include euiYScroll; -} - -.lnsFormula__docsNavGroup { - padding: $euiSize; - - & + & { - border-top: $euiBorderThin; - } -} - -.lnsFormula__docsNavGroupLink { - font-weight: inherit; -} - -.lnsFormula__docsText { - @include euiYScroll; - padding: $euiSize; -} - -.lnsFormula__docsTextGroup, -.lnsFormula__docsTextItem { - margin-top: $euiSizeXXL; -} - -.lnsFormula__docsTextGroup { - border-top: $euiBorderThin; - padding-top: $euiSizeXXL; -} - .lnsFormulaOverflow { // Needs to be higher than the modal and all flyouts z-index: $euiZLevel9 + 1; diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx index dcfdebf5c6b82..7fa1a5edb4f7d 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx @@ -7,6 +7,10 @@ import React, { useCallback, useEffect, useState, useMemo, useRef } from 'react'; import { i18n } from '@kbn/i18n'; +import { + LanguageDocumentationPopover, + LanguageDocumentationPopoverContent, +} from '@kbn/language-documentation-popover'; import { css } from '@emotion/react'; import { EuiButtonIcon, @@ -27,7 +31,7 @@ import { monaco } from '@kbn/monaco'; import classNames from 'classnames'; import { CodeEditor } from '@kbn/kibana-react-plugin/public'; import type { CodeEditorProps } from '@kbn/kibana-react-plugin/public'; -import { TooltipWrapper, useDebounceWithOptions } from '../../../../../../shared_components'; +import { useDebounceWithOptions } from '../../../../../../shared_components'; import { ParamEditorProps } from '../..'; import { getManagedColumnsFrom } from '../../../layer_helpers'; import { ErrorWrapper, runASTValidation, tryToParse } from '../validation'; @@ -45,13 +49,13 @@ import { MARKER, } from './math_completion'; import { LANGUAGE_ID } from './math_tokenization'; -import { MemoizedFormulaHelp } from './formula_help'; import './formula.scss'; import { FormulaIndexPatternColumn } from '../formula'; import { insertOrReplaceFormulaColumn } from '../parse'; import { filterByVisibleOperation, nonNullable } from '../util'; import { getColumnTimeShiftWarnings, getDateHistogramInterval } from '../../../../time_shift_utils'; +import { getDocumentationSections } from './formula_help'; function tableHasData( activeData: ParamEditorProps<FormulaIndexPatternColumn>['activeData'], @@ -126,6 +130,15 @@ export function FormulaEditor({ [operationDefinitionMap] ); + const documentationSections = useMemo( + () => + getDocumentationSections({ + indexPattern, + operationDefinitionMap: visibleOperationsMap, + }), + [indexPattern, visibleOperationsMap] + ); + const baseInterval = 'interval' in dateHistogramInterval ? dateHistogramInterval.interval?.asMilliseconds() @@ -831,47 +844,21 @@ export function FormulaEditor({ </EuiLink> </EuiToolTip> ) : ( - <TooltipWrapper - tooltipContent={i18n.translate( - 'xpack.lens.formula.editorHelpOverlayToolTip', - { - defaultMessage: 'Function reference', - } - )} - condition={!isHelpOpen} - position="top" - delay="regular" - > - <EuiPopover - panelClassName="lnsFormula__docs lnsFormula__docs--overlay" - panelPaddingSize="none" - anchorPosition="leftCenter" - isOpen={isHelpOpen} - closePopover={() => setIsHelpOpen(false)} - button={ - <EuiButtonIcon - className="lnsFormula__editorHelp lnsFormula__editorHelp--overlay" - onClick={() => { - setIsHelpOpen(!isHelpOpen); - }} - iconType="documentation" - color="text" - aria-label={i18n.translate( - 'xpack.lens.formula.editorHelpInlineShowToolTip', - { - defaultMessage: 'Show function reference', - } - )} - /> - } - > - <MemoizedFormulaHelp - isFullscreen={isFullscreen} - indexPattern={indexPattern} - operationDefinitionMap={visibleOperationsMap} - /> - </EuiPopover> - </TooltipWrapper> + <LanguageDocumentationPopover + language="Formula" + sections={documentationSections} + buttonProps={{ + color: 'text', + className: 'lnsFormula__editorHelp lnsFormula__editorHelp--overlay', + 'data-test-subj': 'unifiedTextLangEditor-documentation', + 'aria-label': i18n.translate( + 'xpack.lens.formula.editorHelpInlineShowToolTip', + { + defaultMessage: 'Show function reference', + } + ), + }} + /> )} </EuiFlexItem> @@ -928,12 +915,12 @@ export function FormulaEditor({ </div> </div> + {/* fix the css here */} {isFullscreen && isHelpOpen ? ( - <div className="lnsFormula__docs lnsFormula__docs--inline"> - <MemoizedFormulaHelp - isFullscreen={isFullscreen} - indexPattern={indexPattern} - operationDefinitionMap={visibleOperationsMap} + <div className="lnsFormula__docs documentation__docs--inline"> + <LanguageDocumentationPopoverContent + language="Formula" + sections={documentationSections} /> </div> ) : null} diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_help.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_help.tsx index d0b4b3b0bb173..3c578c646a830 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_help.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_help.tsx @@ -5,21 +5,8 @@ * 2.0. */ -import React, { useEffect, useRef, useState, useMemo } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiPopoverTitle, - EuiText, - EuiListGroupItem, - EuiListGroup, - EuiTitle, - EuiFieldSearch, - EuiHighlight, - EuiSpacer, -} from '@elastic/eui'; import { Markdown } from '@kbn/kibana-react-plugin/public'; import { groupBy } from 'lodash'; import type { IndexPattern } from '../../../../../../types'; @@ -35,24 +22,13 @@ import type { } from '../..'; import type { FormulaIndexPatternColumn } from '../formula'; -function FormulaHelp({ +export function getDocumentationSections({ indexPattern, operationDefinitionMap, - isFullscreen, }: { indexPattern: IndexPattern; operationDefinitionMap: Record<string, GenericOperationDefinition>; - isFullscreen: boolean; }) { - const [selectedFunction, setSelectedFunction] = useState<string | undefined>(); - const scrollTargets = useRef<Record<string, HTMLElement>>({}); - - useEffect(() => { - if (selectedFunction && scrollTargets.current[selectedFunction]) { - scrollTargets.current[selectedFunction].scrollIntoView(); - } - }, [selectedFunction]); - const helpGroups: Array<{ label: string; description?: string; @@ -199,18 +175,14 @@ max(system.network.in.bytes, reducedTimeRange="30m") calculation: calculationFunction, math: mathOperations, comparison: comparisonOperations, - } = useMemo( - () => - groupBy(getPossibleFunctions(indexPattern), (key) => { - if (key in operationDefinitionMap) { - return operationDefinitionMap[key].documentation?.section; - } - if (key in tinymathFunctions) { - return tinymathFunctions[key].section; - } - }), - [operationDefinitionMap, indexPattern] - ); + } = groupBy(getPossibleFunctions(indexPattern), (key) => { + if (key in operationDefinitionMap) { + return operationDefinitionMap[key].documentation?.section; + } + if (key in tinymathFunctions) { + return tinymathFunctions[key].section; + } + }); // Es aggs helpGroups[2].items.push( @@ -259,10 +231,6 @@ max(system.network.in.bytes, reducedTimeRange="30m") ) : null} </> ), - checked: - selectedFunction === `${key}: ${operationDefinitionMap[key].displayName}` - ? ('on' as const) - : undefined, })) ); @@ -277,16 +245,14 @@ max(system.network.in.bytes, reducedTimeRange="30m") items: [], }); - const mathFns = useMemo(() => { - return mathOperations.sort().map((key) => { - const [description, examples] = tinymathFunctions[key].help.split(`\`\`\``); - return { - label: key, - description: description.replace(/\n/g, '\n\n'), - examples: examples ? `\`\`\`${examples}\`\`\`` : '', - }; - }); - }, [mathOperations]); + const mathFns = mathOperations.sort().map((key) => { + const [description, examples] = tinymathFunctions[key].help.split(`\`\`\``); + return { + label: key, + description: description.replace(/\n/g, '\n\n'), + examples: examples ? `\`\`\`${examples}\`\`\`` : '', + }; + }); helpGroups[4].items.push( ...mathFns.map(({ label, description, examples }) => { @@ -313,16 +279,14 @@ max(system.network.in.bytes, reducedTimeRange="30m") items: [], }); - const comparisonFns = useMemo(() => { - return comparisonOperations.sort().map((key) => { - const [description, examples] = tinymathFunctions[key].help.split(`\`\`\``); - return { - label: key, - description: description.replace(/\n/g, '\n\n'), - examples: examples ? `\`\`\`${examples}\`\`\`` : '', - }; - }); - }, [comparisonOperations]); + const comparisonFns = comparisonOperations.sort().map((key) => { + const [description, examples] = tinymathFunctions[key].help.split(`\`\`\``); + return { + label: key, + description: description.replace(/\n/g, '\n\n'), + examples: examples ? `\`\`\`${examples}\`\`\`` : '', + }; + }); helpGroups[5].items.push( ...comparisonFns.map(({ label, description, examples }) => { @@ -339,119 +303,12 @@ max(system.network.in.bytes, reducedTimeRange="30m") }) ); - const [searchText, setSearchText] = useState(''); - - const normalizedSearchText = searchText.trim().toLocaleLowerCase(); - - const filteredHelpGroups = helpGroups - .map((group) => { - const items = group.items.filter((helpItem) => { - return ( - !normalizedSearchText || helpItem.label.toLocaleLowerCase().includes(normalizedSearchText) - ); - }); - return { ...group, items }; - }) - .filter((group) => { - if (group.items.length > 0 || !normalizedSearchText) { - return true; - } - return group.label.toLocaleLowerCase().includes(normalizedSearchText); - }); - - return ( - <> - <EuiPopoverTitle className="lnsFormula__docsHeader" paddingSize="m"> - {i18n.translate('xpack.lens.formulaDocumentation.header', { - defaultMessage: 'Formula reference', - })} - </EuiPopoverTitle> - - <EuiFlexGroup - className="lnsFormula__docsContent" - gutterSize="none" - responsive={false} - alignItems="stretch" - > - <EuiFlexItem className="lnsFormula__docsSidebar" grow={1}> - <EuiFlexGroup - className="lnsFormula__docsSidebarInner" - direction="column" - gutterSize="none" - responsive={false} - > - <EuiFlexItem className="lnsFormula__docsSearch" grow={false}> - <EuiFieldSearch - value={searchText} - onChange={(e) => { - setSearchText(e.target.value); - }} - placeholder={i18n.translate('xpack.lens.formulaSearchPlaceholder', { - defaultMessage: 'Search functions', - })} - /> - </EuiFlexItem> - - <EuiFlexItem className="lnsFormula__docsNav"> - {filteredHelpGroups.map((helpGroup, index) => { - return ( - <nav className="lnsFormula__docsNavGroup" key={helpGroup.label}> - <EuiTitle size="xxs"> - <h6> - <EuiLink - className="lnsFormula__docsNavGroupLink" - color="text" - onClick={() => { - setSelectedFunction(helpGroup.label); - }} - > - <EuiHighlight search={searchText}>{helpGroup.label}</EuiHighlight> - </EuiLink> - </h6> - </EuiTitle> - - {helpGroup.items.length ? ( - <> - <EuiSpacer size="s" /> - - <EuiListGroup gutterSize="none"> - {helpGroup.items.map((helpItem) => { - return ( - <EuiListGroupItem - key={helpItem.label} - label={ - <EuiHighlight search={searchText}>{helpItem.label}</EuiHighlight> - } - size="s" - onClick={() => { - setSelectedFunction(helpItem.label); - }} - /> - ); - })} - </EuiListGroup> - </> - ) : null} - </nav> - ); - })} - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - - <EuiFlexItem className="lnsFormula__docsText" grow={2}> - <EuiText size="s"> - <section - className="lnsFormula__docsTextIntro" - ref={(el) => { - if (el) { - scrollTargets.current[helpGroups[0].label] = el; - } - }} - > - <Markdown - markdown={i18n.translate('xpack.lens.formulaDocumentation.markdown', { - defaultMessage: `## How it works + const sections = { + groups: helpGroups, + initialSection: ( + <Markdown + markdown={i18n.translate('xpack.lens.formulaDocumentation.markdown', { + defaultMessage: `## How it works Lens formulas let you do math using a combination of Elasticsearch aggregations and math functions. There are three main types of functions: @@ -464,9 +321,9 @@ An example formula that uses all of these: \`\`\` round(100 * moving_average( - average(cpu.load.pct), - window=10, - kql='datacenter.name: east*' +average(cpu.load.pct), +window=10, +kql='datacenter.name: east*' )) \`\`\` @@ -482,54 +339,16 @@ queries. If your search has a single quote in it, use a backslash to escape, lik Math functions can take positional arguments, like pow(count(), 3) is the same as count() * count() * count() Use the symbols +, -, /, and * to perform basic math. - `, - description: - 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', - })} - /> - </section> - - {helpGroups.slice(1).map((helpGroup, index) => { - return ( - <section - className="lnsFormula__docsTextGroup" - key={helpGroup.label} - ref={(el) => { - if (el) { - scrollTargets.current[helpGroup.label] = el; - } - }} - > - <h2>{helpGroup.label}</h2> - - <p>{helpGroup.description}</p> - - {helpGroups[index + 1].items.map((helpItem) => { - return ( - <article - className="lnsFormula__docsTextItem" - key={helpItem.label} - ref={(el) => { - if (el) { - scrollTargets.current[helpItem.label] = el; - } - }} - > - {helpItem.description} - </article> - ); - })} - </section> - ); - })} - </EuiText> - </EuiFlexItem> - </EuiFlexGroup> - </> - ); -} + `, + description: + 'Text is in markdown. Do not translate function names, special characters, or field names like sum(bytes)', + })} + /> + ), + }; -export const MemoizedFormulaHelp = React.memo(FormulaHelp); + return sections; +} export function getFunctionSignatureLabel( name: string, diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.test.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.test.ts index 4c1ba662d1861..a80c039064afd 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.test.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.test.ts @@ -103,4 +103,54 @@ describe('createFormulaPublicApi', () => { { indexPattern: {} } ); }); + + test('should pass over advanced parameters as global params for formula', () => { + const baseLayer = getBaseLayer(); + + publicApiHelper.insertOrReplaceFormulaColumn( + 'col', + { + formula: 'count()', + timeScale: 'd', + filter: { query: 'myField: *', language: 'kuery' }, + reducedTimeRange: '30s', + }, + baseLayer, + dataView + ); + + expect(insertOrReplaceFormulaColumn).toHaveBeenCalledWith( + 'col', + { + customLabel: false, + dataType: 'number', + isBucketed: false, + label: 'count()', + operationType: 'formula', + params: { formula: 'count()', format: undefined }, + filter: { + language: 'kuery', + query: 'myField: *', + }, + timeScale: 'd', + reducedTimeRange: '30s', + references: [], + }, + { + columnOrder: ['col1'], + columns: { + col1: { + dataType: 'date', + isBucketed: true, + label: '@timestamp', + operationType: 'date_histogram', + params: { interval: 'auto' }, + scale: 'interval', + }, + }, + indexPatternId: undefined, + }, + { indexPattern: {} } + ); + }); }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.ts index ab17fd81e84ef..56469d61ad8f3 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/formula_public_api.ts @@ -10,6 +10,7 @@ import { Query } from '@kbn/es-query'; import { convertDataViewIntoLensIndexPattern } from '../../../../../data_views_service/loader'; import type { IndexPattern } from '../../../../../types'; import type { PersistedIndexPatternLayer } from '../../../types'; +import type { TimeScaleUnit } from '../../../../../../common/expressions'; import { insertOrReplaceFormulaColumn } from './parse'; @@ -33,6 +34,8 @@ export interface FormulaPublicApi { formula: string; label?: string; filter?: Query; + reducedTimeRange?: string; + timeScale?: TimeScaleUnit; format?: { id: string; params?: { @@ -60,7 +63,12 @@ export const createFormulaPublicApi = (): FormulaPublicApi => { }; return { - insertOrReplaceFormulaColumn: (id, { formula, label, format, filter }, layer, dataView) => { + insertOrReplaceFormulaColumn: ( + id, + { formula, label, format, filter, reducedTimeRange, timeScale }, + layer, + dataView + ) => { const indexPattern = getCachedLensIndexPattern(dataView); return insertOrReplaceFormulaColumn( @@ -73,6 +81,8 @@ export const createFormulaPublicApi = (): FormulaPublicApi => { references: [], isBucketed: false, filter, + reducedTimeRange, + timeScale, params: { formula, format, diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/metrics.tsx index e8c8c54c1cefe..bb15831a31854 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/metrics.tsx @@ -318,7 +318,7 @@ export const averageOperation = buildMetricOperation<AvgIndexPatternColumn>({ quickFunctionDocumentation: i18n.translate( 'xpack.lens.indexPattern.avg.quickFunctionDescription', { - defaultMessage: 'The average value of a number field.', + defaultMessage: 'The mean value of a set of number fields.', } ), }); diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx index d0dd9f6458612..d238fd16b8932 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx @@ -187,7 +187,7 @@ describe('ranges', () => { } beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); beforeEach(() => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts b/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts index fc77aa6520bd0..365d80a3d8285 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/to_expression.ts @@ -7,6 +7,7 @@ import type { IUiSettingsClient } from '@kbn/core/public'; import { partition, uniq } from 'lodash'; +import seedrandom from 'seedrandom'; import { AggFunctionsMapping, EsaggsExpressionFunctionDefinition, @@ -52,7 +53,8 @@ const updatePositionIndex = (currentId: string, newIndex: number) => { function getExpressionForLayer( layer: FormBasedLayer, indexPattern: IndexPattern, - uiSettings: IUiSettingsClient + uiSettings: IUiSettingsClient, + searchSessionId?: string ): ExpressionAstExpression | null { const { columnOrder } = layer; if (columnOrder.length === 0 || !indexPattern) { @@ -392,6 +394,8 @@ function getExpressionForLayer( metricsAtAllLevels: false, partialRows: false, timeFields: allDateHistogramFields, + probability: layer.sampling || 1, + samplerSeed: seedrandom(searchSessionId).int32(), }).toAst(), { type: 'function', @@ -441,13 +445,15 @@ export function toExpression( state: FormBasedPrivateState, layerId: string, indexPatterns: IndexPatternMap, - uiSettings: IUiSettingsClient + uiSettings: IUiSettingsClient, + searchSessionId?: string ) { if (state.layers[layerId]) { return getExpressionForLayer( state.layers[layerId], indexPatterns[state.layers[layerId].indexPatternId], - uiSettings + uiSettings, + searchSessionId ); } diff --git a/x-pack/plugins/lens/public/datasources/form_based/types.ts b/x-pack/plugins/lens/public/datasources/form_based/types.ts index 3c695d5064e3f..0846c96d76dc8 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/types.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/types.ts @@ -54,6 +54,7 @@ export interface FormBasedLayer { linkToLayers?: string[]; // Partial columns represent the temporary invalid states incompleteColumns?: Record<string, IncompleteColumn>; + sampling?: number; } export interface FormBasedPersistedState { diff --git a/x-pack/plugins/lens/public/datasources/text_based/fields_accordion.tsx b/x-pack/plugins/lens/public/datasources/text_based/fields_accordion.tsx index 23f71e991bc82..d02fd98bc9c87 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/fields_accordion.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/fields_accordion.tsx @@ -49,14 +49,18 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ const extraAction = useMemo(() => { if (hasLoaded) { return ( - <EuiNotificationBadge size="m" color={isFiltered ? 'accent' : 'subdued'}> + <EuiNotificationBadge + size="m" + color={isFiltered ? 'accent' : 'subdued'} + data-test-subj={`${id}-count`} + > {fields.length} </EuiNotificationBadge> ); } return <EuiLoadingSpinner size="m" />; - }, [fields.length, hasLoaded, isFiltered]); + }, [fields.length, hasLoaded, id, isFiltered]); return ( <> diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts index cef1bfa96b8a5..25bab766558e3 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.test.ts @@ -217,21 +217,24 @@ describe('IndexPattern Data Source', () => { describe('#removeLayer', () => { it('should remove a layer', () => { expect(TextBasedDatasource.removeLayer(baseState, 'a')).toEqual({ - ...baseState, - layers: { - a: { - columns: [], - allColumns: [ - { - columnId: 'col1', - fieldName: 'Test 1', - meta: { - type: 'number', + removedLayerIds: ['a'], + newState: { + ...baseState, + layers: { + a: { + columns: [], + allColumns: [ + { + columnId: 'col1', + fieldName: 'Test 1', + meta: { + type: 'number', + }, }, - }, - ], - query: { sql: 'SELECT * FROM foo' }, - index: 'foo', + ], + query: { sql: 'SELECT * FROM foo' }, + index: 'foo', + }, }, }, }); diff --git a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx index afe6368477cc9..368036e3ebd50 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx +++ b/x-pack/plugins/lens/public/datasources/text_based/text_based_languages.tsx @@ -281,18 +281,24 @@ export function getTextBasedDatasource({ }; return { - ...state, - layers: newLayers, - fieldList: state.fieldList, + removedLayerIds: [layerId], + newState: { + ...state, + layers: newLayers, + fieldList: state.fieldList, + }, }; }, clearLayer(state: TextBasedPrivateState, layerId: string) { return { - ...state, - layers: { - ...state.layers, - [layerId]: { ...state.layers[layerId], columns: [] }, + removedLayerIds: [], + newState: { + ...state, + layers: { + ...state.layers, + [layerId]: { ...state.layers[layerId], columns: [] }, + }, }, }; }, diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx index af680fbc27160..b8c59712b096f 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx @@ -19,7 +19,7 @@ import { import { act } from 'react-dom/test-utils'; import { DropType } from '../types'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); const dataTransfer = { setData: jest.fn(), diff --git a/x-pack/plugins/lens/public/drag_drop/providers/providers.test.tsx b/x-pack/plugins/lens/public/drag_drop/providers/providers.test.tsx index a8312cc927451..8bbe30e29cb82 100644 --- a/x-pack/plugins/lens/public/drag_drop/providers/providers.test.tsx +++ b/x-pack/plugins/lens/public/drag_drop/providers/providers.test.tsx @@ -9,7 +9,7 @@ import React, { useContext } from 'react'; import { mount } from 'enzyme'; import { RootDragDropProvider, DragContext } from '.'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); describe('RootDragDropProvider', () => { test('reuses contexts for each render', () => { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss index b8ae7fa515190..7b74d0e966410 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss @@ -1,52 +1,3 @@ -@import '@elastic/eui/src/components/flyout/variables'; -@import '@elastic/eui/src/components/flyout/mixins'; - -.lnsDimensionContainer { - // Use the EuiFlyout style - @include euiFlyout; - // But with custom positioning to keep it within the sidebar contents - animation: euiFlyout $euiAnimSpeedNormal $euiAnimSlightResistance; - left: 0; - max-width: none !important; - z-index: $euiZContentMenu; - - @include euiBreakpoint('l', 'xl') { - height: 100% !important; - position: absolute; - top: 0 !important; - } - - .lnsFrameLayout__sidebar-isFullscreen & { - border-left: $euiBorderThin; // Force border regardless of theme in fullscreen - box-shadow: none; - } -} - -.lnsDimensionContainer__header { - padding: $euiSize; - - .lnsFrameLayout__sidebar-isFullscreen & { - display: none; - } -} - -.lnsDimensionContainer__content { - @include euiYScroll; - flex: 1; -} - -.lnsDimensionContainer__footer { - padding: $euiSize; - - .lnsFrameLayout__sidebar-isFullscreen & { - display: none; - } -} - -.lnsBody--overflowHidden { - overflow: hidden; -} - .lnsLayerAddButton:hover { text-decoration: none; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx index 9d71e74eff473..13ba032f9b902 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx @@ -7,44 +7,12 @@ import './dimension_container.scss'; -import React, { useState, useEffect, useCallback } from 'react'; -import { - EuiFlyoutHeader, - EuiFlyoutFooter, - EuiTitle, - EuiButtonIcon, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiFocusTrap, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../../../utils'; - -function fromExcludedClickTarget(event: Event) { - for ( - let node: HTMLElement | null = event.target as HTMLElement; - node !== null; - node = node!.parentElement - ) { - if ( - node.classList!.contains(DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS) || - node.classList!.contains('euiBody-hasPortalContent') || - node.getAttribute('data-euiportal') === 'true' - ) { - return true; - } - } - return false; -} +import React from 'react'; +import { FlyoutContainer } from './flyout_container'; export function DimensionContainer({ - isOpen, - groupLabel, - handleClose, panel, - isFullscreen, - panelRef, + ...props }: { isOpen: boolean; handleClose: () => boolean; @@ -53,107 +21,5 @@ export function DimensionContainer({ isFullscreen: boolean; panelRef: (el: HTMLDivElement) => void; }) { - const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false); - - const closeFlyout = useCallback(() => { - const canClose = handleClose(); - if (canClose) { - setFocusTrapIsEnabled(false); - } - return canClose; - }, [handleClose]); - - useEffect(() => { - document.body.classList.toggle('lnsBody--overflowHidden', isOpen); - return () => { - if (isOpen) { - setFocusTrapIsEnabled(false); - } - document.body.classList.remove('lnsBody--overflowHidden'); - }; - }, [isOpen]); - - if (!isOpen) { - return null; - } - - return ( - <div ref={panelRef}> - <EuiFocusTrap - disabled={!focusTrapIsEnabled} - clickOutsideDisables={false} - onClickOutside={(event) => { - if (isFullscreen || fromExcludedClickTarget(event)) { - return; - } - closeFlyout(); - }} - onEscapeKey={closeFlyout} - > - <div - role="dialog" - aria-labelledby="lnsDimensionContainerTitle" - className="lnsDimensionContainer euiFlyout" - onAnimationEnd={() => { - if (isOpen) { - // EuiFocusTrap interferes with animating elements with absolute position: - // running this onAnimationEnd, otherwise the flyout pushes content when animating - setFocusTrapIsEnabled(true); - } - }} - > - <EuiFlyoutHeader hasBorder className="lnsDimensionContainer__header"> - <EuiFlexGroup gutterSize="m" alignItems="center" responsive={false}> - <EuiFlexItem grow={true}> - <EuiTitle size="xs"> - <h2 - id="lnsDimensionContainerTitle" - className="lnsDimensionContainer__headerTitle" - > - <strong> - {i18n.translate('xpack.lens.configure.configurePanelTitle', { - defaultMessage: '{groupLabel}', - values: { - groupLabel, - }, - })} - </strong> - </h2> - </EuiTitle> - </EuiFlexItem> - - <EuiFlexItem grow={false}> - <EuiButtonIcon - color="text" - data-test-subj="lns-indexPattern-dimensionContainerBack" - className="lnsDimensionContainer__backIcon" - onClick={closeFlyout} - iconType="cross" - aria-label={i18n.translate('xpack.lens.dimensionContainer.closeConfiguration', { - defaultMessage: 'Close configuration', - })} - /> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlyoutHeader> - - <div className="lnsDimensionContainer__content">{panel}</div> - - <EuiFlyoutFooter className="lnsDimensionContainer__footer"> - <EuiButtonEmpty - flush="left" - size="s" - iconType="cross" - onClick={closeFlyout} - data-test-subj="lns-indexPattern-dimensionContainerClose" - > - {i18n.translate('xpack.lens.dimensionContainer.close', { - defaultMessage: 'Close', - })} - </EuiButtonEmpty> - </EuiFlyoutFooter> - </div> - </EuiFocusTrap> - </div> - ); + return <FlyoutContainer {...props}>{panel}</FlyoutContainer>; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.scss new file mode 100644 index 0000000000000..b08eb6281fa0e --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.scss @@ -0,0 +1,48 @@ +@import '@elastic/eui/src/components/flyout/variables'; +@import '@elastic/eui/src/components/flyout/mixins'; + +.lnsDimensionContainer { + // Use the EuiFlyout style + @include euiFlyout; + // But with custom positioning to keep it within the sidebar contents + animation: euiFlyout $euiAnimSpeedNormal $euiAnimSlightResistance; + left: 0; + max-width: none !important; + z-index: $euiZContentMenu; + + @include euiBreakpoint('l', 'xl') { + height: 100% !important; + position: absolute; + top: 0 !important; + } + + .lnsFrameLayout__sidebar-isFullscreen & { + border-left: $euiBorderThin; // Force border regardless of theme in fullscreen + box-shadow: none; + } +} + +.lnsDimensionContainer__header { + padding: $euiSize; + + .lnsFrameLayout__sidebar-isFullscreen & { + display: none; + } +} + +.lnsDimensionContainer__content { + @include euiYScroll; + flex: 1; +} + +.lnsDimensionContainer__footer { + padding: $euiSize; + + .lnsFrameLayout__sidebar-isFullscreen & { + display: none; + } +} + +.lnsBody--overflowHidden { + overflow: hidden; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.tsx new file mode 100644 index 0000000000000..041f59df332f4 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/flyout_container.tsx @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import './flyout_container.scss'; + +import React, { useState, useEffect, useCallback } from 'react'; +import { + EuiFlyoutHeader, + EuiFlyoutFooter, + EuiTitle, + EuiButtonIcon, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFocusTrap, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS } from '../../../utils'; + +function fromExcludedClickTarget(event: Event) { + for ( + let node: HTMLElement | null = event.target as HTMLElement; + node !== null; + node = node!.parentElement + ) { + if ( + node.classList!.contains(DONT_CLOSE_DIMENSION_CONTAINER_ON_CLICK_CLASS) || + node.classList!.contains('euiBody-hasPortalContent') || + node.getAttribute('data-euiportal') === 'true' + ) { + return true; + } + } + return false; +} + +export function FlyoutContainer({ + isOpen, + groupLabel, + handleClose, + isFullscreen, + panelRef, + children, +}: { + isOpen: boolean; + handleClose: () => boolean; + children: React.ReactElement | null; + groupLabel: string; + isFullscreen: boolean; + panelRef: (el: HTMLDivElement) => void; +}) { + const [focusTrapIsEnabled, setFocusTrapIsEnabled] = useState(false); + + const closeFlyout = useCallback(() => { + const canClose = handleClose(); + if (canClose) { + setFocusTrapIsEnabled(false); + } + return canClose; + }, [handleClose]); + + useEffect(() => { + document.body.classList.toggle('lnsBody--overflowHidden', isOpen); + return () => { + if (isOpen) { + setFocusTrapIsEnabled(false); + } + document.body.classList.remove('lnsBody--overflowHidden'); + }; + }, [isOpen]); + + if (!isOpen) { + return null; + } + + return ( + <div ref={panelRef}> + <EuiFocusTrap + disabled={!focusTrapIsEnabled} + clickOutsideDisables={false} + onClickOutside={(event) => { + if (isFullscreen || fromExcludedClickTarget(event)) { + return; + } + closeFlyout(); + }} + onEscapeKey={closeFlyout} + > + <div + role="dialog" + aria-labelledby="lnsDimensionContainerTitle" + className="lnsDimensionContainer euiFlyout" + onAnimationEnd={() => { + if (isOpen) { + // EuiFocusTrap interferes with animating elements with absolute position: + // running this onAnimationEnd, otherwise the flyout pushes content when animating + setFocusTrapIsEnabled(true); + } + }} + > + <EuiFlyoutHeader hasBorder className="lnsDimensionContainer__header"> + <EuiFlexGroup gutterSize="m" alignItems="center" responsive={false}> + <EuiFlexItem grow={true}> + <EuiTitle size="xs"> + <h2 + id="lnsDimensionContainerTitle" + className="lnsDimensionContainer__headerTitle" + > + {i18n.translate('xpack.lens.configure.configurePanelTitle', { + defaultMessage: '{groupLabel}', + values: { + groupLabel, + }, + })} + </h2> + </EuiTitle> + </EuiFlexItem> + + <EuiFlexItem grow={false}> + <EuiButtonIcon + color="text" + data-test-subj="lns-indexPattern-dimensionContainerBack" + className="lnsDimensionContainer__backIcon" + onClick={closeFlyout} + iconType="cross" + aria-label={i18n.translate('xpack.lens.dimensionContainer.closeConfiguration', { + defaultMessage: 'Close configuration', + })} + /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlyoutHeader> + + <div className="lnsDimensionContainer__content">{children}</div> + + <EuiFlyoutFooter className="lnsDimensionContainer__footer"> + <EuiButtonEmpty + flush="left" + size="s" + iconType="cross" + onClick={closeFlyout} + data-test-subj="lns-indexPattern-dimensionContainerClose" + > + {i18n.translate('xpack.lens.dimensionContainer.close', { + defaultMessage: 'Close', + })} + </EuiButtonEmpty> + </EuiFlyoutFooter> + </div> + </EuiFocusTrap> + </div> + ); +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx index bc1c41caa650b..9c32a24eac4dd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions/layer_actions.tsx @@ -104,6 +104,9 @@ const InContextMenuActions = (props: LayerActionsProps) => { closePopover={closePopover} panelPaddingSize="none" anchorPosition="downLeft" + panelProps={{ + 'data-test-subj': 'lnsLayerActionsMenu', + }} > <EuiContextMenuPanel size="s" diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index c27a09b95ede6..acbeb79bbe74d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -46,6 +46,7 @@ import { } from '../../../state_management'; import { onDropForVisualization, shouldRemoveSource } from './buttons/drop_targets_utils'; import { getSharedActions } from './layer_actions/layer_actions'; +import { FlyoutContainer } from './flyout_container'; const initialActiveDimensionState = { isNew: false, @@ -89,6 +90,8 @@ export function LayerPanel( const [activeDimension, setActiveDimension] = useState<ActiveDimensionState>( initialActiveDimensionState ); + const [isPanelSettingsOpen, setPanelSettingsOpen] = useState(false); + const [hideTooltip, setHideTooltip] = useState<boolean>(false); const { @@ -120,6 +123,7 @@ export function LayerPanel( }, [activeVisualization.id]); const panelRef = useRef<HTMLDivElement | null>(null); + const settingsPanelRef = useRef<HTMLDivElement | null>(null); const registerLayerRef = useCallback( (el) => registerNewLayerRef(layerId, el), [layerId, registerNewLayerRef] @@ -316,7 +320,14 @@ export function LayerPanel( ...(activeVisualization.getSupportedActionsForLayer?.( layerId, visualizationState, - updateVisualization + updateVisualization, + () => setPanelSettingsOpen(true) + ) || []), + ...(layerDatasource?.getSupportedActionsForLayer?.( + layerId, + layerDatasourceState, + (newState) => updateDatasource(datasourceId, newState), + () => setPanelSettingsOpen(true) ) || []), ...getSharedActions({ activeVisualization, @@ -332,12 +343,16 @@ export function LayerPanel( [ activeVisualization, core, + datasourceId, isOnlyLayer, isTextBasedLanguage, + layerDatasource, + layerDatasourceState, layerId, layerIndex, onCloneLayer, onRemoveLayer, + updateDatasource, updateVisualization, visualizationState, ] @@ -624,7 +639,42 @@ export function LayerPanel( })} </EuiPanel> </section> - + {(layerDatasource?.renderLayerSettings || activeVisualization?.renderLayerSettings) && ( + <FlyoutContainer + panelRef={(el) => (settingsPanelRef.current = el)} + isOpen={isPanelSettingsOpen} + isFullscreen={false} + groupLabel={i18n.translate('xpack.lens.editorFrame.layerSettingsTitle', { + defaultMessage: 'Layer settings', + })} + handleClose={() => { + // update the current layer settings + setPanelSettingsOpen(false); + return true; + }} + > + <div id={layerId}> + <div className="lnsIndexPatternDimensionEditor--padded lnsIndexPatternDimensionEditor--collapseNext"> + {layerDatasource?.renderLayerSettings && ( + <NativeRenderer + render={layerDatasource.renderLayerSettings} + nativeProps={layerDatasourceConfigProps} + /> + )} + {activeVisualization?.renderLayerSettings && ( + <NativeRenderer + render={activeVisualization?.renderLayerSettings} + nativeProps={{ + ...layerVisualizationConfigProps, + setState: props.updateVisualization, + panelRef: settingsPanelRef, + }} + /> + )} + </div> + </div> + </FlyoutContainer> + )} <DimensionContainer panelRef={(el) => (panelRef.current = el)} isOpen={isDimensionPanelOpen} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts index c9b0358b81a0b..aee10196d5156 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/expression_helpers.ts @@ -11,7 +11,8 @@ import { Visualization, DatasourceMap, DatasourceLayers, IndexPatternMap } from export function getDatasourceExpressionsByLayers( datasourceMap: DatasourceMap, datasourceStates: DatasourceStates, - indexPatterns: IndexPatternMap + indexPatterns: IndexPatternMap, + searchSessionId?: string ): null | Record<string, Ast> { const datasourceExpressions: Array<[string, Ast | string]> = []; @@ -24,7 +25,7 @@ export function getDatasourceExpressionsByLayers( const layers = datasource.getLayers(state); layers.forEach((layerId) => { - const result = datasource.toExpression(state, layerId, indexPatterns); + const result = datasource.toExpression(state, layerId, indexPatterns, searchSessionId); if (result) { datasourceExpressions.push([layerId, result]); } @@ -53,6 +54,7 @@ export function buildExpression({ title, description, indexPatterns, + searchSessionId, }: { title?: string; description?: string; @@ -62,6 +64,7 @@ export function buildExpression({ datasourceStates: DatasourceStates; datasourceLayers: DatasourceLayers; indexPatterns: IndexPatternMap; + searchSessionId?: string; }): Ast | null { if (visualization === null) { return null; @@ -70,7 +73,8 @@ export function buildExpression({ const datasourceExpressionsByLayers = getDatasourceExpressionsByLayers( datasourceMap, datasourceStates, - indexPatterns + indexPatterns, + searchSessionId ); const visualizationExpression = visualization.toExpression( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx index e6b762911d4dc..276b56cc69fff 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx @@ -494,8 +494,8 @@ describe('chart_switch', () => { switchTo('visB', instance); expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith({}, 'a'); - expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith(undefined, 'b'); - expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith(undefined, 'c'); + expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith({}, 'b'); + expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith({}, 'c'); expect(visualizationMap.visB.getSuggestions).toHaveBeenCalledWith( expect.objectContaining({ keptLayerIds: ['a'], diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 38f62b6e928b6..2f2003970171a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -162,6 +162,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ const changesApplied = useLensSelector(selectChangesApplied); const triggerApply = useLensSelector(selectTriggerApplyChanges); const datasourceLayers = useLensSelector((state) => selectDatasourceLayers(state, datasourceMap)); + const searchSessionId = useLensSelector(selectSearchSessionId); const [localState, setLocalState] = useState<WorkspaceState>({ expressionBuildError: undefined, @@ -317,6 +318,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ datasourceStates, datasourceLayers, indexPatterns: dataViews.indexPatterns, + searchSessionId, }); if (ast) { @@ -349,16 +351,17 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ })); } }, [ + configurationValidationError?.length, + missingRefsErrors.length, + unknownVisError, activeVisualization, visualization.state, + visualization.activeId, datasourceMap, datasourceStates, datasourceLayers, - configurationValidationError?.length, - missingRefsErrors.length, - unknownVisError, - visualization.activeId, dataViews.indexPatterns, + searchSessionId, ]); useEffect(() => { diff --git a/x-pack/plugins/lens/public/mocks/datasource_mock.ts b/x-pack/plugins/lens/public/mocks/datasource_mock.ts index cfb93882559ef..986a753a1a4a4 100644 --- a/x-pack/plugins/lens/public/mocks/datasource_mock.ts +++ b/x-pack/plugins/lens/public/mocks/datasource_mock.ts @@ -26,7 +26,7 @@ export function createMockDatasource(id: string): DatasourceMock { return { id: 'testDatasource', - clearLayer: jest.fn((state, _layerId) => state), + clearLayer: jest.fn((state, _layerId) => ({ newState: state, removedLayerIds: [] })), getDatasourceSuggestionsForField: jest.fn((_state, _item, filterFn, _indexPatterns) => []), getDatasourceSuggestionsForVisualizeField: jest.fn( (_state, _indexpatternId, _fieldName, _indexPatterns) => [] @@ -44,7 +44,7 @@ export function createMockDatasource(id: string): DatasourceMock { renderLayerPanel: jest.fn(), toExpression: jest.fn((_frame, _state, _indexPatterns) => null), insertLayer: jest.fn((_state, _newLayerId) => ({})), - removeLayer: jest.fn((_state, _layerId) => {}), + removeLayer: jest.fn((state, layerId) => ({ newState: state, removedLayerIds: [layerId] })), cloneLayer: jest.fn((_state, _layerId, _newLayerId, getNewId) => {}), removeColumn: jest.fn((props) => {}), getLayers: jest.fn((_state) => []), diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts index 934fc0854b6e6..df9d42015632c 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts @@ -39,6 +39,7 @@ describe('lensSlice', () => { let store: EnhancedStore<{ lens: LensAppState }>; beforeEach(() => { store = makeLensStore({}).store; + jest.clearAllMocks(); }); const customQuery = { query: 'custom' } as Query; @@ -275,17 +276,21 @@ describe('lensSlice', () => { return { id: datasourceId, getPublicAPI: () => ({ - datasourceId: 'testDatasource', + datasourceId, getOperationForColumnId: jest.fn(), getTableSpec: jest.fn(), }), getLayers: () => ['layer1'], - clearLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).map((id: string) => + clearLayer: (layerIds: unknown, layerId: string) => ({ + removedLayerIds: [], + newState: (layerIds as string[]).map((id: string) => id === layerId ? `${datasourceId}_clear_${layerId}` : id ), - removeLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).filter((id: string) => id !== layerId), + }), + removeLayer: (layerIds: unknown, layerId: string) => ({ + newState: (layerIds as string[]).filter((id: string) => id !== layerId), + removedLayerIds: [layerId], + }), insertLayer: (layerIds: unknown, layerId: string, layersToLinkTo: string[]) => [ ...(layerIds as string[]), layerId, @@ -317,8 +322,9 @@ describe('lensSlice', () => { (layerIds as string[]).map((id: string) => id === layerId ? `vis_clear_${layerId}` : id ), - removeLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).filter((id: string) => id !== layerId), + removeLayer: jest.fn((layerIds: unknown, layerId: string) => + (layerIds as string[]).filter((id: string) => id !== layerId) + ), getLayerIds: (layerIds: unknown) => layerIds as string[], getLayersToLinkTo: (state, newLayerId) => ['linked-layer-id'], appendLayer: (layerIds: unknown, layerId: string) => [...(layerIds as string[]), layerId], @@ -482,6 +488,54 @@ describe('lensSlice', () => { expect(state.datasourceStates.testDatasource2.state).toEqual(['layer2']); expect(state.stagedPreview).not.toBeDefined(); }); + + it('removeLayer: should remove all layers from visualization that were removed by datasource', () => { + const removedLayerId = 'other-removed-layer'; + + const testDatasource3 = testDatasource('testDatasource3'); + testDatasource3.removeLayer = (layerIds: unknown, layerId: string) => ({ + newState: (layerIds as string[]).filter((id: string) => id !== layerId), + removedLayerIds: [layerId, removedLayerId], + }); + + const localStore = makeLensStore({ + preloadedState: { + activeDatasourceId: 'testDatasource', + datasourceStates: { + ...datasourceStates, + testDatasource3: { + isLoading: false, + state: [], + }, + }, + visualization: { + activeId: activeVisId, + state: ['layer1', 'layer2'], + }, + stagedPreview: { + visualization: { + activeId: activeVisId, + state: ['layer1', 'layer2'], + }, + datasourceStates, + }, + }, + storeDeps: mockStoreDeps({ + visualizationMap: visualizationMap as unknown as VisualizationMap, + datasourceMap: { ...datasourceMap, testDatasource3 } as unknown as DatasourceMap, + }), + }).store; + + localStore.dispatch( + removeOrClearLayer({ + visualizationId: 'testVis', + layerId: 'layer1', + layerIds: ['layer1', 'layer2'], + }) + ); + + expect(visualizationMap[activeVisId].removeLayer).toHaveBeenCalledTimes(2); + }); }); describe('removing a dimension', () => { @@ -546,8 +600,6 @@ describe('lensSlice', () => { datasourceMap: datasourceMap as unknown as DatasourceMap, }), }).store; - - jest.clearAllMocks(); }); it('removes a dimension', () => { diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index b90c5bc965a6b..3f3490906f88c 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -7,7 +7,7 @@ import { createAction, createReducer, current, PayloadAction } from '@reduxjs/toolkit'; import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public'; -import { mapValues } from 'lodash'; +import { mapValues, uniq } from 'lodash'; import { Query } from '@kbn/es-query'; import { History } from 'history'; import { LensEmbeddableInput } from '..'; @@ -400,16 +400,23 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { layerIds.length ) === 'clear'; + let removedLayerIds: string[] = []; + state.datasourceStates = mapValues( state.datasourceStates, (datasourceState, datasourceId) => { const datasource = datasourceMap[datasourceId!]; + + const { newState, removedLayerIds: removedLayerIdsForThisDatasource } = isOnlyLayer + ? datasource.clearLayer(datasourceState.state, layerId) + : datasource.removeLayer(datasourceState.state, layerId); + + removedLayerIds = [...removedLayerIds, ...removedLayerIdsForThisDatasource]; + return { ...datasourceState, ...(datasourceId === state.activeDatasourceId && { - state: isOnlyLayer - ? datasource.clearLayer(datasourceState.state, layerId) - : datasource.removeLayer(datasourceState.state, layerId), + state: newState, }), }; } @@ -419,10 +426,22 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { const currentDataViewsId = activeDataSource.getUsedDataView( state.datasourceStates[state.activeDatasourceId!].state ); - state.visualization.state = - isOnlyLayer || !activeVisualization.removeLayer - ? activeVisualization.clearLayer(state.visualization.state, layerId, currentDataViewsId) - : activeVisualization.removeLayer(state.visualization.state, layerId); + + if (isOnlyLayer || !activeVisualization.removeLayer) { + state.visualization.state = activeVisualization.clearLayer( + state.visualization.state, + layerId, + currentDataViewsId + ); + } + + uniq(removedLayerIds).forEach( + (removedId) => + (state.visualization.state = activeVisualization.removeLayer?.( + state.visualization.state, + removedId + )) + ); }, [changeIndexPattern.type]: ( state, @@ -977,9 +996,12 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { ); }) ?? []; if (layerDatasourceId) { - state.datasourceStates[layerDatasourceId].state = datasourceMap[ - layerDatasourceId - ].removeLayer(current(state).datasourceStates[layerDatasourceId].state, layerId); + const { newState } = datasourceMap[layerDatasourceId].removeLayer( + current(state).datasourceStates[layerDatasourceId].state, + layerId + ); + state.datasourceStates[layerDatasourceId].state = newState; + // TODO - call removeLayer for any extra (linked) layers removed by the datasource } }); }, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index e01c35f3457d9..94a0589f78b08 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -265,8 +265,8 @@ export interface Datasource<T = unknown, P = unknown> { insertLayer: (state: T, newLayerId: string, linkToLayers?: string[]) => T; createEmptyLayer: (indexPatternId: string) => T; - removeLayer: (state: T, layerId: string) => T; - clearLayer: (state: T, layerId: string) => T; + removeLayer: (state: T, layerId: string) => { newState: T; removedLayerIds: string[] }; + clearLayer: (state: T, layerId: string) => { newState: T; removedLayerIds: string[] }; cloneLayer: ( state: T, layerId: string, @@ -301,6 +301,10 @@ export interface Datasource<T = unknown, P = unknown> { }) => T; getSelectedFields?: (state: T) => string[]; + renderLayerSettings?: ( + domElement: Element, + props: DatasourceLayerSettingsProps<T> + ) => ((cleanupElement: Element) => void) | void; renderDataPanel: ( domElement: Element, props: DatasourceDataPanelProps<T> @@ -360,7 +364,8 @@ export interface Datasource<T = unknown, P = unknown> { toExpression: ( state: T, layerId: string, - indexPatterns: IndexPatternMap + indexPatterns: IndexPatternMap, + searchSessionId?: string ) => ExpressionAstExpression | string | null; getDatasourceSuggestionsForField: ( @@ -458,6 +463,13 @@ export interface Datasource<T = unknown, P = unknown> { * Get all the used DataViews from state */ getUsedDataViews: (state: T) => string[]; + + getSupportedActionsForLayer?: ( + layerId: string, + state: T, + setState: StateSetter<T>, + openLayerSettings?: () => void + ) => LayerAction[]; } export interface DatasourceFixAction<T> { @@ -509,6 +521,12 @@ export interface DatasourcePublicAPI { hasDefaultTimeField: () => boolean; } +export interface DatasourceLayerSettingsProps<T = unknown> { + layerId: string; + state: T; + setState: StateSetter<T>; +} + export interface DatasourceDataPanelProps<T = unknown> { state: T; dragDropContext: DragContextState; @@ -715,6 +733,11 @@ export interface VisualizationToolbarProps<T = unknown> { state: T; } +export type VisualizationLayerSettingsProps<T = unknown> = VisualizationConfigProps<T> & { + setState(newState: T | ((currState: T) => T)): void; + panelRef: MutableRefObject<HTMLDivElement | null>; +}; + export type VisualizationDimensionEditorProps<T = unknown> = VisualizationConfigProps<T> & { groupId: string; accessor: string; @@ -1010,7 +1033,8 @@ export interface Visualization<T = unknown, P = unknown> { getSupportedActionsForLayer?: ( layerId: string, state: T, - setState: StateSetter<T> + setState: StateSetter<T>, + openLayerSettings?: () => void ) => LayerAction[]; /** returns the type string of the given layer */ getLayerType: (layerId: string, state?: T) => LayerType | undefined; @@ -1090,6 +1114,11 @@ export interface Visualization<T = unknown, P = unknown> { dropProps: GetDropPropsArgs ) => { dropTypes: DropType[]; nextLabel?: string } | undefined; + renderLayerSettings?: ( + domElement: Element, + props: VisualizationLayerSettingsProps<T> + ) => ((cleanupElement: Element) => void) | void; + /** * Additional editor that gets rendered inside the dimension popover. * This can be used to configure dimension-specific options diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/types.ts b/x-pack/plugins/lens/public/visualizations/heatmap/types.ts index 08913ad25a7d3..6be8d3b6e8d95 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/types.ts +++ b/x-pack/plugins/lens/public/visualizations/heatmap/types.ts @@ -10,7 +10,7 @@ import type { HeatmapArguments } from '@kbn/expression-heatmap-plugin/common'; import type { LayerType } from '../../../common'; export type ChartShapes = 'heatmap'; -export type HeatmapLayerState = HeatmapArguments & { +export type HeatmapLayerState = Omit<HeatmapArguments, 'palette'> & { layerId: string; layerType: LayerType; valueAccessor?: string; diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx index b8e98d03843a9..fc8ef976548b4 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx @@ -17,7 +17,8 @@ import { ThemeServiceStart } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { LayerTypes } from '@kbn/expression-xy-plugin/public'; -import type { OperationMetadata, Visualization } from '../../types'; +import { HeatmapConfiguration } from '@kbn/visualizations-plugin/common'; +import type { OperationMetadata, Suggestion, Visualization } from '../../types'; import type { HeatmapVisualizationState } from './types'; import { getSuggestions } from './suggestions'; import { @@ -33,6 +34,7 @@ import { import { HeatmapToolbar } from './toolbar_component'; import { HeatmapDimensionEditor } from './dimension_editor'; import { getSafePaletteParams } from './utils'; +import { FormBasedPersistedState } from '../..'; const groupLabelForHeatmap = i18n.translate('xpack.lens.heatmapVisualization.heatmapGroupLabel', { defaultMessage: 'Magnitude', @@ -525,4 +527,28 @@ export const getHeatmapVisualization = ({ ] : undefined; }, + + getSuggestionFromConvertToLensContext({ suggestions, context }) { + const allSuggestions = suggestions as Array< + Suggestion<HeatmapVisualizationState, FormBasedPersistedState> + >; + const suggestion: Suggestion<HeatmapVisualizationState, FormBasedPersistedState> = { + ...allSuggestions[0], + datasourceState: { + ...allSuggestions[0].datasourceState, + layers: allSuggestions.reduce( + (acc, s) => ({ + ...acc, + ...s.datasourceState?.layers, + }), + {} + ), + }, + visualizationState: { + ...allSuggestions[0].visualizationState, + ...(context.configuration as HeatmapConfiguration), + }, + }; + return suggestion; + }, }); diff --git a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.test.tsx b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.test.tsx index 9826e60a83bc9..b45eef4ec379e 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.test.tsx @@ -12,7 +12,11 @@ import { OperationDescriptor, VisualizationDimensionEditorProps } from '../../ty import { CustomPaletteParams, PaletteOutput, PaletteRegistry } from '@kbn/coloring'; import { MetricVisualizationState } from './visualization'; -import { DimensionEditor, SupportingVisType } from './dimension_editor'; +import { + DimensionEditor, + DimensionEditorAdditionalSection, + SupportingVisType, +} from './dimension_editor'; import { HTMLAttributes, mount, ReactWrapper, shallow } from 'enzyme'; import { CollapseSetting } from '../../shared_components/collapse_setting'; import { EuiButtonGroup, EuiColorPicker, PropsOf } from '@elastic/eui'; @@ -154,42 +158,6 @@ describe('dimension editor', () => { this.colorPicker.props().onChange!(color, {} as EuiColorPickerOutput); }); } - - private get supportingVisButtonGroup() { - return this._wrapper.find( - 'EuiButtonGroup[data-test-subj="lnsMetric_supporting_visualization_buttons"]' - ) as unknown as ReactWrapper<PropsOf<typeof EuiButtonGroup>>; - } - - public get currentSupportingVis() { - return this.supportingVisButtonGroup - .props() - .idSelected?.split('--')[1] as SupportingVisType; - } - - public isDisabled(type: SupportingVisType) { - return this.supportingVisButtonGroup.props().options.find(({ id }) => id.includes(type)) - ?.isDisabled; - } - - public setSupportingVis(type: SupportingVisType) { - this.supportingVisButtonGroup.props().onChange(`some-id--${type}`); - } - - private get progressDirectionControl() { - return this._wrapper.find( - 'EuiButtonGroup[data-test-subj="lnsMetric_progress_direction_buttons"]' - ) as unknown as ReactWrapper<PropsOf<typeof EuiButtonGroup>>; - } - - public get progressDirectionShowing() { - return this.progressDirectionControl.exists(); - } - - public setProgressDirection(direction: LayoutDirection) { - this.progressDirectionControl.props().onChange(direction); - this._wrapper.update(); - } } const mockSetState = jest.fn(); @@ -266,144 +234,6 @@ describe('dimension editor', () => { `); }); }); - - describe('supporting visualizations', () => { - const stateWOTrend = { - ...metricAccessorState, - trendlineLayerId: undefined, - }; - - describe('reflecting visualization state', () => { - it('should select the correct button', () => { - expect( - getHarnessWithState({ ...stateWOTrend, showBar: false, maxAccessor: undefined }) - .currentSupportingVis - ).toBe<SupportingVisType>('none'); - expect( - getHarnessWithState({ ...stateWOTrend, showBar: true }).currentSupportingVis - ).toBe<SupportingVisType>('bar'); - expect( - getHarnessWithState(metricAccessorState).currentSupportingVis - ).toBe<SupportingVisType>('trendline'); - }); - - it('should disable bar when no max dimension', () => { - expect( - getHarnessWithState({ - ...stateWOTrend, - showBar: false, - maxAccessor: 'something', - }).isDisabled('bar') - ).toBeFalsy(); - expect( - getHarnessWithState({ - ...stateWOTrend, - showBar: false, - maxAccessor: undefined, - }).isDisabled('bar') - ).toBeTruthy(); - }); - - it('should disable trendline when no default time field', () => { - expect( - getHarnessWithState(stateWOTrend, { - hasDefaultTimeField: () => false, - getOperationForColumnId: (id) => ({} as OperationDescriptor), - } as DatasourcePublicAPI).isDisabled('trendline') - ).toBeTruthy(); - expect( - getHarnessWithState(stateWOTrend, { - hasDefaultTimeField: () => true, - getOperationForColumnId: (id) => ({} as OperationDescriptor), - } as DatasourcePublicAPI).isDisabled('trendline') - ).toBeFalsy(); - }); - }); - - it('should disable trendline when a metric dimension has a reduced time range', () => { - expect( - getHarnessWithState(stateWOTrend, { - hasDefaultTimeField: () => true, - getOperationForColumnId: (id) => - ({ hasReducedTimeRange: id === stateWOTrend.metricAccessor } as OperationDescriptor), - } as DatasourcePublicAPI).isDisabled('trendline') - ).toBeTruthy(); - expect( - getHarnessWithState(stateWOTrend, { - hasDefaultTimeField: () => true, - getOperationForColumnId: (id) => - ({ - hasReducedTimeRange: id === stateWOTrend.secondaryMetricAccessor, - } as OperationDescriptor), - } as DatasourcePublicAPI).isDisabled('trendline') - ).toBeTruthy(); - }); - - describe('responding to buttons', () => { - it('enables trendline', () => { - getHarnessWithState(stateWOTrend).setSupportingVis('trendline'); - - expect(mockSetState).toHaveBeenCalledWith({ ...stateWOTrend, showBar: false }); - expect(props.addLayer).toHaveBeenCalledWith('metricTrendline'); - - expectCalledBefore(mockSetState, props.addLayer as jest.Mock); - }); - - it('enables bar', () => { - getHarnessWithState(metricAccessorState).setSupportingVis('bar'); - - expect(mockSetState).toHaveBeenCalledWith({ ...metricAccessorState, showBar: true }); - expect(props.removeLayer).toHaveBeenCalledWith(metricAccessorState.trendlineLayerId); - - expectCalledBefore(mockSetState, props.removeLayer as jest.Mock); - }); - - it('selects none from bar', () => { - getHarnessWithState(stateWOTrend).setSupportingVis('none'); - - expect(mockSetState).toHaveBeenCalledWith({ ...stateWOTrend, showBar: false }); - expect(props.removeLayer).not.toHaveBeenCalled(); - }); - - it('selects none from trendline', () => { - getHarnessWithState(metricAccessorState).setSupportingVis('none'); - - expect(mockSetState).toHaveBeenCalledWith({ ...metricAccessorState, showBar: false }); - expect(props.removeLayer).toHaveBeenCalledWith(metricAccessorState.trendlineLayerId); - - expectCalledBefore(mockSetState, props.removeLayer as jest.Mock); - }); - }); - - describe('progress bar direction controls', () => { - it('hides direction controls if bar not showing', () => { - expect( - getHarnessWithState({ ...stateWOTrend, showBar: false }).progressDirectionShowing - ).toBeFalsy(); - }); - - it('toggles progress direction', () => { - const harness = getHarnessWithState(metricAccessorState); - - expect(harness.progressDirectionShowing).toBeTruthy(); - expect(harness.currentState.progressDirection).toBe('vertical'); - - harness.setProgressDirection('horizontal'); - harness.setProgressDirection('vertical'); - harness.setProgressDirection('horizontal'); - - expect(mockSetState).toHaveBeenCalledTimes(3); - expect(mockSetState.mock.calls.map((args) => args[0].progressDirection)) - .toMatchInlineSnapshot(` - Array [ - "horizontal", - "vertical", - "horizontal", - ] - `); - }); - }); - }); }); describe('secondary metric dimension', () => { @@ -628,4 +458,235 @@ describe('dimension editor', () => { `); }); }); + + describe('additional section', () => { + const accessor = 'primary-metric-col-id'; + const metricAccessorState = { ...fullState, metricAccessor: accessor }; + + class Harness { + public _wrapper; + + constructor( + wrapper: ReactWrapper<HTMLAttributes, unknown, React.Component<{}, {}, unknown>> + ) { + this._wrapper = wrapper; + } + + private get rootComponent() { + return this._wrapper.find(DimensionEditorAdditionalSection); + } + + public get currentState() { + return this.rootComponent.props().state; + } + + private get supportingVisButtonGroup() { + return this._wrapper.find( + 'EuiButtonGroup[data-test-subj="lnsMetric_supporting_visualization_buttons"]' + ) as unknown as ReactWrapper<PropsOf<typeof EuiButtonGroup>>; + } + + public get currentSupportingVis() { + return this.supportingVisButtonGroup + .props() + .idSelected?.split('--')[1] as SupportingVisType; + } + + public isDisabled(type: SupportingVisType) { + return this.supportingVisButtonGroup.props().options.find(({ id }) => id.includes(type)) + ?.isDisabled; + } + + public setSupportingVis(type: SupportingVisType) { + this.supportingVisButtonGroup.props().onChange(`some-id--${type}`); + } + + private get progressDirectionControl() { + return this._wrapper.find( + 'EuiButtonGroup[data-test-subj="lnsMetric_progress_direction_buttons"]' + ) as unknown as ReactWrapper<PropsOf<typeof EuiButtonGroup>>; + } + + public get progressDirectionShowing() { + return this.progressDirectionControl.exists(); + } + + public setProgressDirection(direction: LayoutDirection) { + this.progressDirectionControl.props().onChange(direction); + this._wrapper.update(); + } + } + + const mockSetState = jest.fn(); + + const getHarnessWithState = (state: MetricVisualizationState, datasource = props.datasource) => + new Harness( + mountWithIntl( + <DimensionEditorAdditionalSection + {...props} + datasource={datasource} + state={state} + setState={mockSetState} + accessor={accessor} + /> + ) + ); + + it.each([ + { name: 'secondary metric', accessor: metricAccessorState.secondaryMetricAccessor }, + { name: 'max', accessor: metricAccessorState.maxAccessor }, + { name: 'break down by', accessor: metricAccessorState.breakdownByAccessor }, + ])('doesnt show for the following dimension: %s', ({ accessor: testAccessor }) => { + expect( + shallow( + <DimensionEditorAdditionalSection + {...props} + state={metricAccessorState} + setState={mockSetState} + accessor={testAccessor} + /> + ).isEmptyRender() + ).toBeTruthy(); + }); + + describe('supporting visualizations', () => { + const stateWOTrend = { + ...metricAccessorState, + trendlineLayerId: undefined, + }; + + describe('reflecting visualization state', () => { + it('should select the correct button', () => { + expect( + getHarnessWithState({ ...stateWOTrend, showBar: false, maxAccessor: undefined }) + .currentSupportingVis + ).toBe<SupportingVisType>('none'); + expect( + getHarnessWithState({ ...stateWOTrend, showBar: true }).currentSupportingVis + ).toBe<SupportingVisType>('bar'); + expect( + getHarnessWithState(metricAccessorState).currentSupportingVis + ).toBe<SupportingVisType>('trendline'); + }); + + it('should disable bar when no max dimension', () => { + expect( + getHarnessWithState({ + ...stateWOTrend, + showBar: false, + maxAccessor: 'something', + }).isDisabled('bar') + ).toBeFalsy(); + expect( + getHarnessWithState({ + ...stateWOTrend, + showBar: false, + maxAccessor: undefined, + }).isDisabled('bar') + ).toBeTruthy(); + }); + + it('should disable trendline when no default time field', () => { + expect( + getHarnessWithState(stateWOTrend, { + hasDefaultTimeField: () => false, + getOperationForColumnId: (id) => ({} as OperationDescriptor), + } as DatasourcePublicAPI).isDisabled('trendline') + ).toBeTruthy(); + expect( + getHarnessWithState(stateWOTrend, { + hasDefaultTimeField: () => true, + getOperationForColumnId: (id) => ({} as OperationDescriptor), + } as DatasourcePublicAPI).isDisabled('trendline') + ).toBeFalsy(); + }); + }); + + it('should disable trendline when a metric dimension has a reduced time range', () => { + expect( + getHarnessWithState(stateWOTrend, { + hasDefaultTimeField: () => true, + getOperationForColumnId: (id) => + ({ + hasReducedTimeRange: id === stateWOTrend.metricAccessor, + } as OperationDescriptor), + } as DatasourcePublicAPI).isDisabled('trendline') + ).toBeTruthy(); + expect( + getHarnessWithState(stateWOTrend, { + hasDefaultTimeField: () => true, + getOperationForColumnId: (id) => + ({ + hasReducedTimeRange: id === stateWOTrend.secondaryMetricAccessor, + } as OperationDescriptor), + } as DatasourcePublicAPI).isDisabled('trendline') + ).toBeTruthy(); + }); + + describe('responding to buttons', () => { + it('enables trendline', () => { + getHarnessWithState(stateWOTrend).setSupportingVis('trendline'); + + expect(mockSetState).toHaveBeenCalledWith({ ...stateWOTrend, showBar: false }); + expect(props.addLayer).toHaveBeenCalledWith('metricTrendline'); + + expectCalledBefore(mockSetState, props.addLayer as jest.Mock); + }); + + it('enables bar', () => { + getHarnessWithState(metricAccessorState).setSupportingVis('bar'); + + expect(mockSetState).toHaveBeenCalledWith({ ...metricAccessorState, showBar: true }); + expect(props.removeLayer).toHaveBeenCalledWith(metricAccessorState.trendlineLayerId); + + expectCalledBefore(mockSetState, props.removeLayer as jest.Mock); + }); + + it('selects none from bar', () => { + getHarnessWithState(stateWOTrend).setSupportingVis('none'); + + expect(mockSetState).toHaveBeenCalledWith({ ...stateWOTrend, showBar: false }); + expect(props.removeLayer).not.toHaveBeenCalled(); + }); + + it('selects none from trendline', () => { + getHarnessWithState(metricAccessorState).setSupportingVis('none'); + + expect(mockSetState).toHaveBeenCalledWith({ ...metricAccessorState, showBar: false }); + expect(props.removeLayer).toHaveBeenCalledWith(metricAccessorState.trendlineLayerId); + + expectCalledBefore(mockSetState, props.removeLayer as jest.Mock); + }); + }); + + describe('progress bar direction controls', () => { + it('hides direction controls if bar not showing', () => { + expect( + getHarnessWithState({ ...stateWOTrend, showBar: false }).progressDirectionShowing + ).toBeFalsy(); + }); + + it('toggles progress direction', () => { + const harness = getHarnessWithState(metricAccessorState); + + expect(harness.progressDirectionShowing).toBeTruthy(); + expect(harness.currentState.progressDirection).toBe('vertical'); + + harness.setProgressDirection('horizontal'); + harness.setProgressDirection('vertical'); + harness.setProgressDirection('horizontal'); + + expect(mockSetState).toHaveBeenCalledTimes(3); + expect(mockSetState.mock.calls.map((args) => args[0].progressDirection)) + .toMatchInlineSnapshot(` + Array [ + "horizontal", + "vertical", + "horizontal", + ] + `); + }); + }); + }); + }); }); diff --git a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx index 4ca35b060e023..6571e1bb2c7d6 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/dimension_editor.tsx @@ -17,6 +17,8 @@ import { EuiColorPicker, euiPaletteColorBlind, EuiSpacer, + EuiText, + useEuiTheme, } from '@elastic/eui'; import { LayoutDirection } from '@elastic/charts'; import React, { useCallback, useState } from 'react'; @@ -30,6 +32,7 @@ import { } from '@kbn/coloring'; import { getDataBoundsForPalette } from '@kbn/expression-metric-vis-plugin/public'; import { getColumnByAccessor } from '@kbn/visualizations-plugin/common/utils'; +import { css } from '@emotion/react'; import { isNumericFieldForDatatable } from '../../../common/expressions/datatable/utils'; import { applyPaletteParams, @@ -263,170 +266,8 @@ function PrimaryMetricEditor(props: SubProps) { const togglePalette = () => setIsPaletteOpen(!isPaletteOpen); - const supportingVisLabel = i18n.translate('xpack.lens.metric.supportingVis.label', { - defaultMessage: 'Supporting visualization', - }); - - const hasDefaultTimeField = props.datasource?.hasDefaultTimeField(); - const metricHasReducedTimeRange = Boolean( - state.metricAccessor && - props.datasource?.getOperationForColumnId(state.metricAccessor)?.hasReducedTimeRange - ); - const secondaryMetricHasReducedTimeRange = Boolean( - state.secondaryMetricAccessor && - props.datasource?.getOperationForColumnId(state.secondaryMetricAccessor)?.hasReducedTimeRange - ); - - const supportingVisHelpTexts: string[] = []; - - const supportsTrendline = - hasDefaultTimeField && !metricHasReducedTimeRange && !secondaryMetricHasReducedTimeRange; - - if (!supportsTrendline) { - supportingVisHelpTexts.push( - !hasDefaultTimeField - ? i18n.translate('xpack.lens.metric.supportingVis.needDefaultTimeField', { - defaultMessage: 'Use a data view with a default time field to enable trend lines.', - }) - : metricHasReducedTimeRange - ? i18n.translate('xpack.lens.metric.supportingVis.metricHasReducedTimeRange', { - defaultMessage: - 'Remove the reduced time range on this dimension to enable trend lines.', - }) - : secondaryMetricHasReducedTimeRange - ? i18n.translate('xpack.lens.metric.supportingVis.secondaryMetricHasReducedTimeRange', { - defaultMessage: - 'Remove the reduced time range on the secondary metric dimension to enable trend lines.', - }) - : '' - ); - } - - if (!state.maxAccessor) { - supportingVisHelpTexts.push( - i18n.translate('xpack.lens.metric.summportingVis.needMaxDimension', { - defaultMessage: 'Add a maximum dimension to enable the progress bar.', - }) - ); - } - - const buttonIdPrefix = `${idPrefix}--`; - return ( <> - <EuiFormRow - display="columnCompressed" - fullWidth - label={supportingVisLabel} - helpText={supportingVisHelpTexts.map((text) => ( - <div>{text}</div> - ))} - > - <EuiButtonGroup - isFullWidth - buttonSize="compressed" - legend={supportingVisLabel} - data-test-subj="lnsMetric_supporting_visualization_buttons" - options={[ - { - id: `${buttonIdPrefix}none`, - label: i18n.translate('xpack.lens.metric.supportingVisualization.none', { - defaultMessage: 'None', - }), - 'data-test-subj': 'lnsMetric_supporting_visualization_none', - }, - { - id: `${buttonIdPrefix}trendline`, - label: i18n.translate('xpack.lens.metric.supportingVisualization.trendline', { - defaultMessage: 'Trend line', - }), - isDisabled: !supportsTrendline, - 'data-test-subj': 'lnsMetric_supporting_visualization_trendline', - }, - { - id: `${buttonIdPrefix}bar`, - label: i18n.translate('xpack.lens.metric.supportingVisualization.bar', { - defaultMessage: 'Bar', - }), - isDisabled: !state.maxAccessor, - 'data-test-subj': 'lnsMetric_supporting_visualization_bar', - }, - ]} - idSelected={`${buttonIdPrefix}${ - state.trendlineLayerId ? 'trendline' : showingBar(state) ? 'bar' : 'none' - }`} - onChange={(id) => { - const supportingVisualizationType = id.split('--')[1] as SupportingVisType; - - switch (supportingVisualizationType) { - case 'trendline': - setState({ - ...state, - showBar: false, - }); - props.addLayer('metricTrendline'); - break; - case 'bar': - setState({ - ...state, - showBar: true, - }); - if (state.trendlineLayerId) props.removeLayer(state.trendlineLayerId); - break; - case 'none': - setState({ - ...state, - showBar: false, - }); - if (state.trendlineLayerId) props.removeLayer(state.trendlineLayerId); - break; - } - }} - /> - </EuiFormRow> - {showingBar(state) && ( - <EuiFormRow - label={i18n.translate('xpack.lens.metric.progressDirectionLabel', { - defaultMessage: 'Bar direction', - })} - fullWidth - display="columnCompressed" - > - <EuiButtonGroup - isFullWidth - buttonSize="compressed" - legend={i18n.translate('xpack.lens.metric.progressDirectionLabel', { - defaultMessage: 'Bar direction', - })} - data-test-subj="lnsMetric_progress_direction_buttons" - name="alignment" - options={[ - { - id: `${idPrefix}vertical`, - label: i18n.translate('xpack.lens.metric.progressDirection.vertical', { - defaultMessage: 'Vertical', - }), - 'data-test-subj': 'lnsMetric_progress_bar_vertical', - }, - { - id: `${idPrefix}horizontal`, - label: i18n.translate('xpack.lens.metric.progressDirection.horizontal', { - defaultMessage: 'Horizontal', - }), - 'data-test-subj': 'lnsMetric_progress_bar_horizontal', - }, - ]} - idSelected={`${idPrefix}${state.progressDirection ?? 'vertical'}`} - onChange={(id) => { - const newDirection = id.replace(idPrefix, '') as LayoutDirection; - setState({ - ...state, - progressDirection: newDirection, - }); - }} - /> - </EuiFormRow> - )} <EuiFormRow display="columnCompressed" fullWidth @@ -580,3 +421,203 @@ function StaticColorControls({ state, setState }: Pick<Props, 'state' | 'setStat </EuiFormRow> ); } + +export function DimensionEditorAdditionalSection({ + state, + datasource, + setState, + addLayer, + removeLayer, + accessor, +}: VisualizationDimensionEditorProps<MetricVisualizationState>) { + const { euiTheme } = useEuiTheme(); + + if (accessor !== state.metricAccessor) { + return null; + } + + const idPrefix = htmlIdGenerator()(); + + const hasDefaultTimeField = datasource?.hasDefaultTimeField(); + const metricHasReducedTimeRange = Boolean( + state.metricAccessor && + datasource?.getOperationForColumnId(state.metricAccessor)?.hasReducedTimeRange + ); + const secondaryMetricHasReducedTimeRange = Boolean( + state.secondaryMetricAccessor && + datasource?.getOperationForColumnId(state.secondaryMetricAccessor)?.hasReducedTimeRange + ); + + const supportingVisHelpTexts: string[] = []; + + const supportsTrendline = + hasDefaultTimeField && !metricHasReducedTimeRange && !secondaryMetricHasReducedTimeRange; + + if (!supportsTrendline) { + supportingVisHelpTexts.push( + !hasDefaultTimeField + ? i18n.translate('xpack.lens.metric.supportingVis.needDefaultTimeField', { + defaultMessage: + 'Line visualizations require use of a data view with a default time field.', + }) + : metricHasReducedTimeRange + ? i18n.translate('xpack.lens.metric.supportingVis.metricHasReducedTimeRange', { + defaultMessage: + 'Line visualizations cannot be used when a reduced time range is applied to the primary metric.', + }) + : secondaryMetricHasReducedTimeRange + ? i18n.translate('xpack.lens.metric.supportingVis.secondaryMetricHasReducedTimeRange', { + defaultMessage: + 'Line visualizations cannot be used when a reduced time range is applied to the secondary metric.', + }) + : '' + ); + } + + if (!state.maxAccessor) { + supportingVisHelpTexts.push( + i18n.translate('xpack.lens.metric.summportingVis.needMaxDimension', { + defaultMessage: 'Bar visualizations require a maximum value to be defined.', + }) + ); + } + + const buttonIdPrefix = `${idPrefix}--`; + + return ( + <div className="lnsIndexPatternDimensionEditor--padded lnsIndexPatternDimensionEditor--collapseNext"> + <EuiText + size="s" + css={css` + margin-bottom: ${euiTheme.size.base}; + `} + > + <h4> + {i18n.translate('xpack.lens.metric.supportingVis.label', { + defaultMessage: 'Supporting visualization', + })} + </h4> + </EuiText> + + <> + <EuiFormRow + display="columnCompressed" + fullWidth + label={i18n.translate('xpack.lens.metric.supportingVis.type', { + defaultMessage: 'Type', + })} + helpText={supportingVisHelpTexts.map((text) => ( + <p>{text}</p> + ))} + > + <EuiButtonGroup + isFullWidth + buttonSize="compressed" + legend={i18n.translate('xpack.lens.metric.supportingVis.type', { + defaultMessage: 'Type', + })} + data-test-subj="lnsMetric_supporting_visualization_buttons" + options={[ + { + id: `${buttonIdPrefix}none`, + label: i18n.translate('xpack.lens.metric.supportingVisualization.none', { + defaultMessage: 'None', + }), + 'data-test-subj': 'lnsMetric_supporting_visualization_none', + }, + { + id: `${buttonIdPrefix}trendline`, + label: i18n.translate('xpack.lens.metric.supportingVisualization.trendline', { + defaultMessage: 'Line', + }), + isDisabled: !supportsTrendline, + 'data-test-subj': 'lnsMetric_supporting_visualization_trendline', + }, + { + id: `${buttonIdPrefix}bar`, + label: i18n.translate('xpack.lens.metric.supportingVisualization.bar', { + defaultMessage: 'Bar', + }), + isDisabled: !state.maxAccessor, + 'data-test-subj': 'lnsMetric_supporting_visualization_bar', + }, + ]} + idSelected={`${buttonIdPrefix}${ + state.trendlineLayerId ? 'trendline' : showingBar(state) ? 'bar' : 'none' + }`} + onChange={(id) => { + const supportingVisualizationType = id.split('--')[1] as SupportingVisType; + + switch (supportingVisualizationType) { + case 'trendline': + setState({ + ...state, + showBar: false, + }); + addLayer('metricTrendline'); + break; + case 'bar': + setState({ + ...state, + showBar: true, + }); + if (state.trendlineLayerId) removeLayer(state.trendlineLayerId); + break; + case 'none': + setState({ + ...state, + showBar: false, + }); + if (state.trendlineLayerId) removeLayer(state.trendlineLayerId); + break; + } + }} + /> + </EuiFormRow> + {showingBar(state) && ( + <EuiFormRow + label={i18n.translate('xpack.lens.metric.progressDirectionLabel', { + defaultMessage: 'Bar orientation', + })} + fullWidth + display="columnCompressed" + > + <EuiButtonGroup + isFullWidth + buttonSize="compressed" + legend={i18n.translate('xpack.lens.metric.progressDirectionLabel', { + defaultMessage: 'Bar orientation', + })} + data-test-subj="lnsMetric_progress_direction_buttons" + name="alignment" + options={[ + { + id: `${idPrefix}vertical`, + label: i18n.translate('xpack.lens.metric.progressDirection.vertical', { + defaultMessage: 'Vertical', + }), + 'data-test-subj': 'lnsMetric_progress_bar_vertical', + }, + { + id: `${idPrefix}horizontal`, + label: i18n.translate('xpack.lens.metric.progressDirection.horizontal', { + defaultMessage: 'Horizontal', + }), + 'data-test-subj': 'lnsMetric_progress_bar_horizontal', + }, + ]} + idSelected={`${idPrefix}${state.progressDirection ?? 'vertical'}`} + onChange={(id) => { + const newDirection = id.replace(idPrefix, '') as LayoutDirection; + setState({ + ...state, + progressDirection: newDirection, + }); + }} + /> + </EuiFormRow> + )} + </> + </div> + ); +} diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts index c6194956c2f0b..2a89ef784492d 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.test.ts @@ -758,6 +758,16 @@ describe('metric visualization', () => { `); }); + it('removes all accessors from a layer', () => { + const chk = visualization.removeLayer!(fullState, 'first'); + expect(chk.metricAccessor).toBeUndefined(); + expect(chk.trendlineLayerId).toBeUndefined(); + expect(chk.trendlineLayerType).toBeUndefined(); + expect(chk.trendlineMetricAccessor).toBeUndefined(); + expect(chk.trendlineTimeAccessor).toBeUndefined(); + expect(chk.trendlineBreakdownByAccessor).toBeUndefined(); + }); + it('appends a trendline layer', () => { const newLayerId = 'new-layer-id'; const chk = visualization.appendLayer!(fullState, newLayerId, 'metricTrendline', ''); @@ -767,6 +777,7 @@ describe('metric visualization', () => { it('removes trendline layer', () => { const chk = visualization.removeLayer!(fullStateWTrend, fullStateWTrend.trendlineLayerId); + expect(chk.metricAccessor).toBe('metric-col-id'); expect(chk.trendlineLayerId).toBeUndefined(); expect(chk.trendlineLayerType).toBeUndefined(); expect(chk.trendlineMetricAccessor).toBeUndefined(); diff --git a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx index 95c9785b2e9fe..eac08b22c9ea4 100644 --- a/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/metric/visualization.tsx @@ -30,7 +30,7 @@ import { Suggestion, } from '../../types'; import { GROUP_ID, LENS_METRIC_ID } from './constants'; -import { DimensionEditor } from './dimension_editor'; +import { DimensionEditor, DimensionEditorAdditionalSection } from './dimension_editor'; import { Toolbar } from './toolbar'; import { generateId } from '../../id_generator'; import { FormatSelectorOptions } from '../../datasources/form_based/dimension_panel/format_selector'; @@ -440,9 +440,10 @@ export const getMetricVisualization = ({ return { ...state, trendlineLayerId: layerId, trendlineLayerType: layerType }; }, - removeLayer(state) { + removeLayer(state, layerId) { const newState: MetricVisualizationState = { ...state, + ...(state.layerId === layerId && { metricAccessor: undefined }), trendlineLayerId: undefined, trendlineLayerType: undefined, trendlineMetricAccessor: undefined, @@ -453,6 +454,10 @@ export const getMetricVisualization = ({ return newState; }, + getRemoveOperation(state, layerId) { + return layerId === state.trendlineLayerId ? 'remove' : 'clear'; + }, + getLayersToLinkTo(state, newLayerId: string): string[] { return newLayerId === state.trendlineLayerId ? [state.layerId] : []; }, @@ -616,6 +621,17 @@ export const getMetricVisualization = ({ ); }, + renderDimensionEditorAdditionalSection(domElement, props) { + render( + <KibanaThemeProvider theme$={theme.theme$}> + <I18nProvider> + <DimensionEditorAdditionalSection {...props} /> + </I18nProvider> + </KibanaThemeProvider>, + domElement + ); + }, + getErrorMessages(state) { // Is it possible to break it? return undefined; diff --git a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts index 78e9393e5755d..ad04d467741c4 100644 --- a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts @@ -78,6 +78,7 @@ export type ColorDynamicOptions = { customColorRamp?: OrdinalColorStop[]; useCustomColorRamp?: boolean; dataMappingFunction?: DATA_MAPPING_FUNCTION; + invert?: boolean; // category color properties colorCategory?: string; // TODO move color category palettes to constants and make ENUM type @@ -176,6 +177,7 @@ export type SizeDynamicOptions = { maxSize: number; field?: StylePropertyField; fieldMetaOptions: FieldMetaOptions; + invert?: boolean; }; export type SizeStaticOptions = { diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.test.ts b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.test.ts new file mode 100644 index 0000000000000..19aa122aa9f95 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.test.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 { MAX_ZOOM, MIN_ZOOM } from '../../../../common/constants'; +import { LayerGroup } from './layer_group'; +import { ILayer } from '../layer'; + +describe('getMinZoom', () => { + test('should return MIN_ZOOM when there are no children', async () => { + const layerGroup = new LayerGroup({ layerDescriptor: LayerGroup.createDescriptor({}) }); + expect(layerGroup.getMinZoom()).toBe(MIN_ZOOM); + }); + + test('should return smallest child.getMinZoom()', async () => { + const layerGroup = new LayerGroup({ layerDescriptor: LayerGroup.createDescriptor({}) }); + layerGroup.setChildren([ + { + getMinZoom: () => { + return 1; + }, + } as unknown as ILayer, + { + getMinZoom: () => { + return 4; + }, + } as unknown as ILayer, + ]); + expect(layerGroup.getMinZoom()).toBe(1); + }); +}); + +describe('getMaxZoom', () => { + test('should return MAX_ZOOM when there are no children', async () => { + const layerGroup = new LayerGroup({ layerDescriptor: LayerGroup.createDescriptor({}) }); + expect(layerGroup.getMaxZoom()).toBe(MAX_ZOOM); + }); + + test('should return largest child.getMaxZoom()', async () => { + const layerGroup = new LayerGroup({ layerDescriptor: LayerGroup.createDescriptor({}) }); + layerGroup.setChildren([ + { + getMaxZoom: () => { + return 18; + }, + } as unknown as ILayer, + { + getMaxZoom: () => { + return 20; + }, + } as unknown as ILayer, + ]); + expect(layerGroup.getMaxZoom()).toBe(20); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx index c0e3c4ee56402..c1a2a2964a315 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx @@ -207,20 +207,34 @@ export class LayerGroup implements ILayer { return zoom >= this.getMinZoom() && zoom <= this.getMaxZoom(); } + /* + * Returns smallest min from children or MIN_ZOOM when there are no children + */ getMinZoom(): number { - let min = MIN_ZOOM; + let min: number | undefined; this._children.forEach((child) => { - min = Math.max(min, child.getMinZoom()); + if (min !== undefined) { + min = Math.min(min, child.getMinZoom()); + } else { + min = child.getMinZoom(); + } }); - return min; + return min !== undefined ? min : MIN_ZOOM; } + /* + * Returns largest max from children or MAX_ZOOM when there are no children + */ getMaxZoom(): number { - let max = MAX_ZOOM; + let max: number | undefined; this._children.forEach((child) => { - max = Math.min(max, child.getMaxZoom()); + if (max !== undefined) { + max = Math.max(max, child.getMaxZoom()); + } else { + max = child.getMaxZoom(); + } }); - return max; + return max !== undefined ? max : MAX_ZOOM; } getMinSourceZoom(): number { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts index a7afbbd7e85eb..6d6ff971e7067 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts @@ -40,6 +40,7 @@ describe('ESGeoGridSource', () => { const mockIndexPatternService = { get() { return { + getIndexPattern: () => 'foo-*', fields: { getByName() { return { @@ -310,7 +311,7 @@ describe('ESGeoGridSource', () => { const tileUrl = await mvtGeogridSource.getTileUrl(vectorSourceRequestMeta, '1234', false); expect(tileUrl).toEqual( - "rootdir/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=undefined&gridPrecision=8&hasLabels=false&requestBody=(foobar%3AES_DSL_PLACEHOLDER%2Cparams%3A('0'%3A('0'%3Aindex%2C'1'%3A(fields%3A()))%2C'1'%3A('0'%3Asize%2C'1'%3A0)%2C'2'%3A('0'%3Afilter%2C'1'%3A!())%2C'3'%3A('0'%3Aquery)%2C'4'%3A('0'%3Aindex%2C'1'%3A(fields%3A()))%2C'5'%3A('0'%3Aquery%2C'1'%3A(language%3AKQL%2Cquery%3A''))%2C'6'%3A('0'%3Aaggs%2C'1'%3A())))&renderAs=heatmap&token=1234" + "rootdir/api/maps/mvt/getGridTile/{z}/{x}/{y}.pbf?geometryFieldName=bar&index=foo-*&gridPrecision=8&hasLabels=false&requestBody=(foobar%3AES_DSL_PLACEHOLDER%2Cparams%3A('0'%3A('0'%3Aindex%2C'1'%3A(fields%3A()))%2C'1'%3A('0'%3Asize%2C'1'%3A0)%2C'2'%3A('0'%3Afilter%2C'1'%3A!())%2C'3'%3A('0'%3Aquery)%2C'4'%3A('0'%3Aindex%2C'1'%3A(fields%3A()))%2C'5'%3A('0'%3Aquery%2C'1'%3A(language%3AKQL%2Cquery%3A''))%2C'6'%3A('0'%3Aaggs%2C'1'%3A())))&renderAs=heatmap&token=1234" ); }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx index b0a230bebc3cd..314a2f1205bda 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx @@ -504,9 +504,9 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo refreshToken: string, hasLabels: boolean ): Promise<string> { - const indexPattern = await this.getIndexPattern(); + const dataView = await this.getIndexPattern(); const searchSource = await this.makeSearchSource(searchFilters, 0); - searchSource.setField('aggs', this.getValueAggsDsl(indexPattern)); + searchSource.setField('aggs', this.getValueAggsDsl(dataView)); const mvtUrlServicePath = getHttp().basePath.prepend( `/${GIS_API_PATH}/${MVT_GETGRIDTILE_API_PATH}/{z}/{x}/{y}.pbf` @@ -514,7 +514,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo return `${mvtUrlServicePath}\ ?geometryFieldName=${this._descriptor.geoField}\ -&index=${indexPattern.title}\ +&index=${dataView.getIndexPattern()}\ &gridPrecision=${this._getGeoGridPrecisionResolutionDelta()}\ &hasLabels=${hasLabels}\ &requestBody=${encodeMvtResponseBody(searchSource.getSearchRequestBody())}\ diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts index e374989e07892..3d41082dcdf73 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts @@ -37,6 +37,7 @@ describe('ESSearchSource', () => { const mockIndexPatternService = { get() { return { + getIndexPattern: () => 'foobar-title-*', title: 'foobar-title-*', fields: { getByName() { 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 f55f5be747dcd..ae639e9f2121d 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 @@ -463,12 +463,9 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource } async getSourceIndexList(): Promise<string[]> { - await this.getIndexPattern(); - if (!(this.indexPattern && this.indexPattern.title)) { - return []; - } + const dataView = await this.getIndexPattern(); try { - const { success, matchingIndexes } = await getMatchingIndexes(this.indexPattern.title); + const { success, matchingIndexes } = await getMatchingIndexes(dataView.getIndexPattern()); return success ? matchingIndexes : []; } catch (e) { // Fail silently @@ -497,11 +494,8 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource } async _isDrawingIndex(): Promise<boolean> { - await this.getIndexPattern(); - if (!(this.indexPattern && this.indexPattern.title)) { - return false; - } - const { success, isDrawingIndex } = await getIsDrawLayer(this.indexPattern.title); + const dataView = await this.getIndexPattern(); + const { success, isDrawingIndex } = await getIsDrawLayer(dataView.getIndexPattern()); return success && isDrawingIndex; } @@ -511,8 +505,8 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource } async getMaxResultWindow(): Promise<number> { - const indexPattern = await this.getIndexPattern(); - const indexSettings = await loadIndexSettings(indexPattern.title); + const dataView = await this.getIndexPattern(); + const indexSettings = await loadIndexSettings(dataView.getIndexPattern()); return indexSettings.maxResultWindow; } @@ -832,8 +826,8 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource refreshToken: string, hasLabels: boolean ): Promise<string> { - const indexPattern = await this.getIndexPattern(); - const indexSettings = await loadIndexSettings(indexPattern.title); + const dataView = await this.getIndexPattern(); + const indexSettings = await loadIndexSettings(dataView.getIndexPattern()); const searchSource = await this.makeSearchSource(searchFilters, indexSettings.maxResultWindow); // searchSource calls dataView.getComputedFields to seed docvalueFields @@ -855,7 +849,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource return fieldName !== this._descriptor.geoField; }) .map((fieldName) => { - const field = indexPattern.fields.getByName(fieldName); + const field = dataView.fields.getByName(fieldName); return field && field.type === 'date' ? { field: fieldName, @@ -876,7 +870,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource return `${mvtUrlServicePath}\ ?geometryFieldName=${this._descriptor.geoField}\ -&index=${indexPattern.title}\ +&index=${dataView.getIndexPattern()}\ &hasLabels=${hasLabels}\ &requestBody=${encodeMvtResponseBody(requestBody)}\ &token=${refreshToken}`; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx index 2cbc47e4e9c76..a5786cb0bed0d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/top_hits_form.tsx @@ -70,8 +70,8 @@ export class TopHitsForm extends Component<Props, State> { async loadIndexSettings() { try { - const indexPattern = await getIndexPatternService().get(this.props.indexPatternId); - const { maxInnerResultWindow } = await loadIndexSettings(indexPattern!.title); + const dataView = await getIndexPatternService().get(this.props.indexPatternId); + const { maxInnerResultWindow } = await loadIndexSettings(dataView.getIndexPattern()); if (this._isMounted) { this.setState({ maxInnerResultWindow }); } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx index ccd3b3913a085..230c24bb820a7 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/scaling_form.tsx @@ -64,8 +64,8 @@ export class ScalingForm extends Component<Props, State> { async loadIndexSettings() { try { - const indexPattern = await getIndexPatternService().get(this.props.indexPatternId); - const { maxResultWindow } = await loadIndexSettings(indexPattern!.title); + const dataView = await getIndexPatternService().get(this.props.indexPatternId); + const { maxResultWindow } = await loadIndexSettings(dataView.getIndexPattern()); if (this._isMounted) { this.setState({ maxResultWindow: maxResultWindow.toLocaleString() }); } diff --git a/x-pack/plugins/maps/public/classes/styles/color_palettes.test.ts b/x-pack/plugins/maps/public/classes/styles/color_palettes.test.ts index 6c98cdc476502..d5a6ea694b3e0 100644 --- a/x-pack/plugins/maps/public/classes/styles/color_palettes.test.ts +++ b/x-pack/plugins/maps/public/classes/styles/color_palettes.test.ts @@ -13,7 +13,7 @@ import { } from './color_palettes'; describe('getColorPalette', () => { - it('Should create RGB color ramp', () => { + test('Should create RGB color ramp', () => { expect(getColorPalette('Blues')).toEqual([ '#ecf1f7', '#d9e3ef', @@ -28,14 +28,14 @@ describe('getColorPalette', () => { }); describe('getColorRampCenterColor', () => { - it('Should get center color from color ramp', () => { + test('Should get center color from color ramp', () => { expect(getColorRampCenterColor('Blues')).toBe('#9eb9d8'); }); }); describe('getOrdinalMbColorRampStops', () => { - it('Should create color stops for custom range', () => { - expect(getOrdinalMbColorRampStops('Blues', 0, 1000)).toEqual([ + test('Should create color stops', () => { + expect(getOrdinalMbColorRampStops('Blues', 0, 1000, false)).toEqual([ 0, '#ecf1f7', 125, @@ -55,13 +55,34 @@ describe('getOrdinalMbColorRampStops', () => { ]); }); - it('Should snap to end of color stops for identical range', () => { - expect(getOrdinalMbColorRampStops('Blues', 23, 23)).toEqual([23, '#6092c0']); + test('Should create inverted color stops', () => { + expect(getOrdinalMbColorRampStops('Blues', 0, 1000, true)).toEqual([ + 0, + '#6092c0', + 125, + '#769fc8', + 250, + '#8bacd0', + 375, + '#9eb9d8', + 500, + '#b2c7df', + 625, + '#c5d5e7', + 750, + '#d9e3ef', + 875, + '#ecf1f7', + ]); + }); + + test('Should snap to end of color stops for identical range', () => { + expect(getOrdinalMbColorRampStops('Blues', 23, 23, false)).toEqual([23, '#6092c0']); }); }); describe('getPercentilesMbColorRampStops', () => { - it('Should create color stops for custom range', () => { + test('Should create color stops', () => { const percentiles = [ { percentile: '50.0', value: 5567.83 }, { percentile: '75.0', value: 8069 }, @@ -69,7 +90,7 @@ describe('getPercentilesMbColorRampStops', () => { { percentile: '95.0', value: 11145.5 }, { percentile: '99.0', value: 16958.18 }, ]; - expect(getPercentilesMbColorRampStops('Blues', percentiles)).toEqual([ + expect(getPercentilesMbColorRampStops('Blues', percentiles, false)).toEqual([ 5567.83, '#e0e8f2', 8069, @@ -82,4 +103,26 @@ describe('getPercentilesMbColorRampStops', () => { '#6092c0', ]); }); + + test('Should create inverted color stops', () => { + const percentiles = [ + { percentile: '50.0', value: 5567.83 }, + { percentile: '75.0', value: 8069 }, + { percentile: '90.0', value: 9581.13 }, + { percentile: '95.0', value: 11145.5 }, + { percentile: '99.0', value: 16958.18 }, + ]; + expect(getPercentilesMbColorRampStops('Blues', percentiles, true)).toEqual([ + 5567.83, + '#6092c0', + 8069, + '#82a7cd', + 9581.13, + '#a2bcd9', + 11145.5, + '#c2d2e6', + 16958.18, + '#e0e8f2', + ]); + }); }); diff --git a/x-pack/plugins/maps/public/classes/styles/color_palettes.ts b/x-pack/plugins/maps/public/classes/styles/color_palettes.ts index 6fa68047acbed..45cd1d988fe6b 100644 --- a/x-pack/plugins/maps/public/classes/styles/color_palettes.ts +++ b/x-pack/plugins/maps/public/classes/styles/color_palettes.ts @@ -153,7 +153,7 @@ export function getColorPalette(colorPaletteId: string): string[] { const colorPalette = COLOR_PALETTES.find(({ value }: COLOR_PALETTE) => { return value === colorPaletteId; }); - return colorPalette ? (colorPalette.palette as string[]) : []; + return colorPalette ? [...(colorPalette.palette as string[])] : []; } export function getColorRampCenterColor(colorPaletteId: string): string | null { @@ -169,7 +169,8 @@ export function getColorRampCenterColor(colorPaletteId: string): string | null { export function getOrdinalMbColorRampStops( colorPaletteId: string | null, min: number, - max: number + max: number, + invert: boolean ): Array<number | string> | null { if (!colorPaletteId) { return null; @@ -180,6 +181,10 @@ export function getOrdinalMbColorRampStops( } const palette = getColorPalette(colorPaletteId); + if (invert) { + palette.reverse(); + } + if (palette.length === 0) { return null; } @@ -203,7 +208,8 @@ export function getOrdinalMbColorRampStops( // [ stop_input_1: number, stop_output_1: color, stop_input_n: number, stop_output_n: color ] export function getPercentilesMbColorRampStops( colorPaletteId: string | null, - percentiles: PercentilesFieldMeta + percentiles: PercentilesFieldMeta, + invert: boolean ): Array<number | string> | null { if (!colorPaletteId) { return null; @@ -213,13 +219,17 @@ export function getPercentilesMbColorRampStops( return value === colorPaletteId; }); - return paletteObject - ? paletteObject - .getPalette(percentiles.length) - .reduce((accu: Array<number | string>, stopColor: string, idx: number) => { - return [...accu, percentiles[idx].value, stopColor]; - }, []) - : null; + if (!paletteObject) { + return null; + } + + const palette = paletteObject.getPalette(percentiles.length); + if (invert) { + palette.reverse(); + } + return palette.reduce((accu: Array<number | string>, stopColor: string, idx: number) => { + return [...accu, percentiles[idx].value, stopColor]; + }, []); } export function getLinearGradient(colorStrings: string[]): string { diff --git a/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.tsx b/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.tsx index b6a0f901b3db6..cf753da8dba0e 100644 --- a/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.tsx +++ b/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.tsx @@ -15,6 +15,7 @@ interface Props { maxLabel: string | number; propertyLabel: string; fieldLabel: string; + invert: boolean; } export function RangedStyleLegendRow({ @@ -23,6 +24,7 @@ export function RangedStyleLegendRow({ maxLabel, propertyLabel, fieldLabel, + invert, }: Props) { return ( <div> @@ -41,12 +43,12 @@ export function RangedStyleLegendRow({ <EuiFlexGroup gutterSize="xs" justifyContent="spaceBetween"> <EuiFlexItem grow={true}> <EuiText size="xs"> - <small>{minLabel}</small> + <small>{invert ? maxLabel : minLabel}</small> </EuiText> </EuiFlexItem> <EuiFlexItem grow={true}> <EuiText textAlign="right" size="xs"> - <small>{maxLabel}</small> + <small>{invert ? minLabel : maxLabel}</small> </EuiText> </EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.tsx b/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.tsx index 5459c55ed95c8..390e48408d747 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.tsx +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/components/legend/heatmap_legend.tsx @@ -58,6 +58,7 @@ export class HeatmapLegend extends Component<Props, State> { })} propertyLabel={HEATMAP_COLOR_RAMP_LABEL} fieldLabel={this.state.label} + invert={false} /> ); } diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx index a2abbad8cddf0..f4413eea15ac8 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx @@ -96,7 +96,8 @@ export class HeatmapStyle implements IStyle { const colorStops = getOrdinalMbColorRampStops( this._descriptor.colorRampName, MIN_RANGE, - MAX_RANGE + MAX_RANGE, + false ); if (colorStops) { mbMap.setPaintProperty(layerId, 'heatmap-color', [ diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js b/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js index 4549367e53470..1409444e0742f 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/color/color_map_select.js @@ -129,16 +129,26 @@ export class ColorMapSelect extends Component { ); } + _getColorPalettes() { + if (this.props.colorMapType === COLOR_MAP_TYPE.CATEGORICAL) { + return CATEGORICAL_COLOR_PALETTES; + } + + return this.props.invert + ? NUMERICAL_COLOR_PALETTES.map((paletteProps) => { + return { + ...paletteProps, + palette: [...paletteProps.palette].reverse(), + }; + }) + : NUMERICAL_COLOR_PALETTES; + } + _renderColorMapSelections() { if (this.props.isCustomOnly) { return null; } - const palettes = - this.props.colorMapType === COLOR_MAP_TYPE.ORDINAL - ? NUMERICAL_COLOR_PALETTES - : CATEGORICAL_COLOR_PALETTES; - const palettesWithCustom = [ { value: CUSTOM_COLOR_MAP, @@ -153,7 +163,7 @@ export class ColorMapSelect extends Component { type: 'text', 'data-test-subj': `colorMapSelectOption_${CUSTOM_COLOR_MAP}`, }, - ...palettes, + ...this._getColorPalettes(), ]; const toggle = this.props.showColorMapTypeToggle ? ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.js b/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.tsx similarity index 50% rename from x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.tsx index a2dba66d3956b..fd0f367d2954d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/color/dynamic_color_form.tsx @@ -6,12 +6,40 @@ */ import _ from 'lodash'; -import React, { Fragment } from 'react'; +import React, { ChangeEvent, ReactNode } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + EuiSwitch, + EuiSwitchEvent, +} from '@elastic/eui'; import { FieldSelect } from '../field_select'; +// @ts-expect-error import { ColorMapSelect } from './color_map_select'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { OtherCategoryColorPicker } from './other_category_color_picker'; -import { CATEGORICAL_DATA_TYPES, COLOR_MAP_TYPE } from '../../../../../../common/constants'; +import { + CategoryColorStop, + ColorDynamicOptions, + OrdinalColorStop, +} from '../../../../../../common/descriptor_types'; +import { + CATEGORICAL_DATA_TYPES, + COLOR_MAP_TYPE, + VECTOR_STYLES, +} from '../../../../../../common/constants'; +import { StyleField } from '../../style_fields_helper'; +import { DynamicColorProperty } from '../../properties/dynamic_color_property'; + +interface Props { + fields: StyleField[]; + onDynamicStyleChange: (propertyName: VECTOR_STYLES, options: ColorDynamicOptions) => void; + staticDynamicSelect?: ReactNode; + styleProperty: DynamicColorProperty; + swatches: string[]; +} export function DynamicColorForm({ fields, @@ -19,10 +47,20 @@ export function DynamicColorForm({ staticDynamicSelect, styleProperty, swatches, -}) { +}: Props) { const styleOptions = styleProperty.getOptions(); - const onColorMapSelect = ({ color, customColorMap, type, useCustomColorMap }) => { + const onColorMapSelect = ({ + color, + customColorMap, + type, + useCustomColorMap, + }: { + color?: null | string; + customColorMap?: OrdinalColorStop[] | CategoryColorStop[]; + type: COLOR_MAP_TYPE; + useCustomColorMap: boolean; + }) => { const newColorOptions = { ...styleOptions, type, @@ -30,7 +68,7 @@ export function DynamicColorForm({ if (type === COLOR_MAP_TYPE.ORDINAL) { newColorOptions.useCustomColorRamp = useCustomColorMap; if (customColorMap) { - newColorOptions.customColorRamp = customColorMap; + newColorOptions.customColorRamp = customColorMap as OrdinalColorStop[]; } if (color) { newColorOptions.color = color; @@ -38,7 +76,7 @@ export function DynamicColorForm({ } else { newColorOptions.useCustomColorPalette = useCustomColorMap; if (customColorMap) { - newColorOptions.customColorPalette = customColorMap; + newColorOptions.customColorPalette = customColorMap as CategoryColorStop[]; } if (color) { newColorOptions.colorCategory = color; @@ -48,7 +86,11 @@ export function DynamicColorForm({ onDynamicStyleChange(styleProperty.getStyleName(), newColorOptions); }; - const onFieldChange = async ({ field }) => { + const onFieldChange = ({ field }: { field: StyleField | null }) => { + if (!field) { + return; + } + const { name, origin, type: fieldType } = field; const defaultColorMapType = CATEGORICAL_DATA_TYPES.includes(fieldType) ? COLOR_MAP_TYPE.CATEGORICAL @@ -60,21 +102,28 @@ export function DynamicColorForm({ }); }; - const onColorMapTypeChange = async (e) => { - const colorMapType = e.target.value; + const onColorMapTypeChange = (e: ChangeEvent<HTMLSelectElement>) => { + const colorMapType = e.target.value as COLOR_MAP_TYPE; onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, type: colorMapType, }); }; - const onOtherCategoryColorChange = (color) => { + const onOtherCategoryColorChange = (color: string) => { onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, otherCategoryColor: color, }); }; + const onInvertChange = (event: EuiSwitchEvent) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + invert: event.target.checked, + }); + }; + const getField = () => { const fieldName = styleProperty.getFieldName(); if (!fieldName) { @@ -92,10 +141,11 @@ export function DynamicColorForm({ return null; } + const invert = styleOptions.invert === undefined ? false : styleOptions.invert; const showColorMapTypeToggle = !CATEGORICAL_DATA_TYPES.includes(field.type); - if (styleProperty.isOrdinal()) { - return ( + return styleProperty.isOrdinal() ? ( + <> <ColorMapSelect isCustomOnly={!field.supportsAutoDomain} onChange={onColorMapSelect} @@ -107,37 +157,49 @@ export function DynamicColorForm({ styleProperty={styleProperty} showColorMapTypeToggle={showColorMapTypeToggle} swatches={swatches} + invert={invert} /> - ); - } else if (styleProperty.isCategorical()) { - return ( - <> - <ColorMapSelect - isCustomOnly={!field.supportsAutoDomain} - onColorMapTypeChange={onColorMapTypeChange} - onChange={onColorMapSelect} - colorMapType={COLOR_MAP_TYPE.CATEGORICAL} - colorPaletteId={styleOptions.colorCategory} - customColorMap={styleOptions.customColorPalette} - useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)} - styleProperty={styleProperty} - showColorMapTypeToggle={showColorMapTypeToggle} - swatches={swatches} - /> - <OtherCategoryColorPicker - onChange={onOtherCategoryColorChange} - color={styleOptions.otherCategoryColor} - /> - </> - ); - } + {!!styleOptions.useCustomColorRamp ? null : ( + <EuiFormRow display="columnCompressedSwitch"> + <EuiSwitch + label={i18n.translate('xpack.maps.style.revereseColorsLabel', { + defaultMessage: `Reverse colors`, + })} + checked={invert} + onChange={onInvertChange} + compressed + /> + </EuiFormRow> + )} + </> + ) : ( + <> + <ColorMapSelect + isCustomOnly={!field.supportsAutoDomain} + onColorMapTypeChange={onColorMapTypeChange} + onChange={onColorMapSelect} + colorMapType={COLOR_MAP_TYPE.CATEGORICAL} + colorPaletteId={styleOptions.colorCategory} + customColorMap={styleOptions.customColorPalette} + useCustomColorMap={_.get(styleOptions, 'useCustomColorPalette', false)} + styleProperty={styleProperty} + showColorMapTypeToggle={showColorMapTypeToggle} + swatches={swatches} + invert={false} + /> + <OtherCategoryColorPicker + onChange={onOtherCategoryColorChange} + color={styleOptions.otherCategoryColor} + /> + </> + ); }; return ( - <Fragment> + <> <EuiFlexGroup gutterSize="xs" justifyContent="flexEnd"> <EuiFlexItem grow={false} className="mapStyleSettings__fixedBox"> - {staticDynamicSelect} + {staticDynamicSelect ? staticDynamicSelect : null} </EuiFlexItem> <EuiFlexItem> <FieldSelect @@ -151,6 +213,6 @@ export function DynamicColorForm({ </EuiFlexGroup> <EuiSpacer size="s" /> {renderColorMapSelect()} - </Fragment> + </> ); } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.js b/x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.tsx similarity index 61% rename from x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.tsx index 02716e7994c4d..20f854916a636 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/color/static_color_form.tsx @@ -5,24 +5,34 @@ * 2.0. */ -import React from 'react'; +import React, { ReactNode } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { VECTOR_STYLES } from '../../../../../../common/constants'; +import { ColorStaticOptions } from '../../../../../../common/descriptor_types'; import { MbValidatedColorPicker } from './mb_validated_color_picker'; +import { StaticColorProperty } from '../../properties/static_color_property'; + +interface Props { + onStaticStyleChange: (propertyName: VECTOR_STYLES, options: ColorStaticOptions) => void; + staticDynamicSelect?: ReactNode; + styleProperty: StaticColorProperty; + swatches: string[]; +} export function StaticColorForm({ onStaticStyleChange, staticDynamicSelect, styleProperty, swatches, -}) { - const onColorChange = (color) => { +}: Props) { + const onColorChange = (color: string) => { onStaticStyleChange(styleProperty.getStyleName(), { color }); }; return ( <EuiFlexGroup gutterSize="xs" justifyContent="flexEnd"> <EuiFlexItem grow={false} className="mapStyleSettings__fixedBox"> - {staticDynamicSelect} + {staticDynamicSelect ? staticDynamicSelect : null} </EuiFlexItem> <EuiFlexItem> <MbValidatedColorPicker diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx index 14989bbdc165c..40a331def7fe0 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx @@ -9,10 +9,10 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { Props, StylePropEditor } from '../style_prop_editor'; -// @ts-expect-error import { DynamicColorForm } from './dynamic_color_form'; -// @ts-expect-error import { StaticColorForm } from './static_color_form'; +import { DynamicColorProperty } from '../../properties/dynamic_color_property'; +import { StaticColorProperty } from '../../properties/static_color_property'; import { ColorDynamicOptions, ColorStaticOptions } from '../../../../../../common/descriptor_types'; type ColorEditorProps = Omit<Props<ColorStaticOptions, ColorDynamicOptions>, 'children'> & { @@ -21,9 +21,9 @@ type ColorEditorProps = Omit<Props<ColorStaticOptions, ColorDynamicOptions>, 'ch export function VectorStyleColorEditor(props: ColorEditorProps) { const colorForm = props.styleProperty.isDynamic() ? ( - <DynamicColorForm {...props} /> + <DynamicColorForm {...props} styleProperty={props.styleProperty as DynamicColorProperty} /> ) : ( - <StaticColorForm {...props} /> + <StaticColorForm {...props} styleProperty={props.styleProperty as StaticColorProperty} /> ); return ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/marker_size_legend.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/marker_size_legend.tsx index c54a42b529a20..11deb6e55942e 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/marker_size_legend.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/marker_size_legend.tsx @@ -91,6 +91,7 @@ export class MarkerSizeLegend extends Component<Props, State> { if (!fieldMeta || !options) { return null; } + const invert = options.invert === undefined ? false : options.invert; const circleStyle = { fillOpacity: 0, @@ -136,14 +137,18 @@ export class MarkerSizeLegend extends Component<Props, State> { // Markers interpolated by area instead of radius to be more consistent with how the human eye+brain perceive shapes // and their visual relevance // This function mirrors output of maplibre expression created from DynamicSizeProperty.getMbSizeExpression - const value = Math.pow(percentage * Math.sqrt(fieldMeta!.delta), 2) + fieldMeta!.min; + const scaledWidth = Math.pow(percentage * Math.sqrt(fieldMeta!.delta), 2); + const value = invert ? fieldMeta!.max - scaledWidth : scaledWidth + fieldMeta!.min; return fieldMeta!.delta > 3 ? Math.round(value) : value; } const markers = []; if (fieldMeta.delta > 0) { - const smallestMarker = makeMarker(options.minSize, this._formatValue(fieldMeta.min)); + const smallestMarker = makeMarker( + options.minSize, + this._formatValue(invert ? fieldMeta.max : fieldMeta.min) + ); markers.push(smallestMarker); const markerDelta = options.maxSize - options.minSize; @@ -156,7 +161,10 @@ export class MarkerSizeLegend extends Component<Props, State> { } } - const largestMarker = makeMarker(options.maxSize, this._formatValue(fieldMeta.max)); + const largestMarker = makeMarker( + options.maxSize, + this._formatValue(invert ? fieldMeta.min : fieldMeta.max) + ); markers.push(largestMarker); return ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx index 2578a908bb68c..048d1bf12a218 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.tsx @@ -138,6 +138,9 @@ export class OrdinalLegend extends Component<Props, State> { this.props.style.isFieldMetaEnabled() && fieldMeta.isMaxOutsideStdRange ? `> ${max}` : max; } + const options = this.props.style.getOptions(); + const invert = options.invert === undefined ? false : options.invert; + return ( <RangedStyleLegendRow header={header} @@ -145,6 +148,7 @@ export class OrdinalLegend extends Component<Props, State> { maxLabel={maxLabel} propertyLabel={this.props.style.getDisplayStyleName()} fieldLabel={this.state.label} + invert={invert} /> ); } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.js b/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.js deleted file mode 100644 index a32ab43f6e070..0000000000000 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.js +++ /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 React, { Fragment } from 'react'; -import { FieldSelect } from '../field_select'; -import { SizeRangeSelector } from './size_range_selector'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; - -export function DynamicSizeForm({ - fields, - onDynamicStyleChange, - staticDynamicSelect, - styleProperty, -}) { - const styleOptions = styleProperty.getOptions(); - - const onFieldChange = ({ field }) => { - onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); - }; - - const onSizeRangeChange = ({ minSize, maxSize }) => { - onDynamicStyleChange(styleProperty.getStyleName(), { - ...styleOptions, - minSize, - maxSize, - }); - }; - - let sizeRange; - if (styleOptions.field && styleOptions.field.name) { - sizeRange = ( - <SizeRangeSelector - onChange={onSizeRangeChange} - minSize={styleOptions.minSize} - maxSize={styleOptions.maxSize} - showLabels - compressed - /> - ); - } - - return ( - <Fragment> - <EuiFlexGroup gutterSize="xs" justifyContent="flexEnd"> - <EuiFlexItem grow={false} className="mapStyleSettings__fixedBox"> - {staticDynamicSelect} - </EuiFlexItem> - <EuiFlexItem> - <FieldSelect - styleName={styleProperty.getStyleName()} - fields={fields} - selectedFieldName={styleProperty.getFieldName()} - onChange={onFieldChange} - compressed - /> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer size="s" /> - {sizeRange} - </Fragment> - ); -} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.tsx new file mode 100644 index 0000000000000..8efe05bc2644c --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/size/dynamic_size_form.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ReactNode } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + EuiSwitch, + EuiSwitchEvent, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FieldSelect } from '../field_select'; +import { SizeRangeSelector } from './size_range_selector'; +import { SizeDynamicOptions } from '../../../../../../common/descriptor_types'; +import { VECTOR_STYLES } from '../../../../../../common/constants'; +import { DynamicSizeProperty } from '../../properties/dynamic_size_property'; +import { StyleField } from '../../style_fields_helper'; + +interface Props { + fields: StyleField[]; + onDynamicStyleChange: (propertyName: VECTOR_STYLES, options: SizeDynamicOptions) => void; + staticDynamicSelect?: ReactNode; + styleProperty: DynamicSizeProperty; +} + +export function DynamicSizeForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}: Props) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }: { field: StyleField | null }) => { + if (field) { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + field: { name: field.name, origin: field.origin }, + }); + } + }; + + const onSizeRangeChange = ({ minSize, maxSize }: { minSize: number; maxSize: number }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + minSize, + maxSize, + }); + }; + + const onInvertChange = (event: EuiSwitchEvent) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + invert: event.target.checked, + }); + }; + + let sizeRange; + if (styleOptions.field && styleOptions.field.name) { + sizeRange = ( + <> + <SizeRangeSelector + onChange={onSizeRangeChange} + minSize={styleOptions.minSize} + maxSize={styleOptions.maxSize} + showLabels + compressed + /> + <EuiFormRow display="columnCompressedSwitch"> + <EuiSwitch + label={i18n.translate('xpack.maps.style.revereseSizeLabel', { + defaultMessage: `Reverse size`, + })} + checked={!!styleOptions.invert} + onChange={onInvertChange} + compressed + /> + </EuiFormRow> + </> + ); + } + + return ( + <> + <EuiFlexGroup gutterSize="xs" justifyContent="flexEnd"> + <EuiFlexItem grow={false} className="mapStyleSettings__fixedBox"> + {staticDynamicSelect} + </EuiFlexItem> + <EuiFlexItem> + <FieldSelect + styleName={styleProperty.getStyleName()} + fields={fields} + selectedFieldName={styleProperty.getFieldName()} + onChange={onFieldChange} + compressed + /> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="s" /> + {sizeRange} + </> + ); +} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.js b/x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.tsx similarity index 74% rename from x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.tsx index b47e22fcd318c..59bdfa3be5938 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/size/size_range_selector.tsx @@ -6,13 +6,19 @@ */ import React from 'react'; -import PropTypes from 'prop-types'; import { ValidatedDualRange } from '@kbn/kibana-react-plugin/public'; -import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults'; +import { EuiDualRangeProps } from '@elastic/eui/src/components/form/range/dual_range'; import { i18n } from '@kbn/i18n'; +import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults'; -export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }) { - const onSizeChange = ([min, max]) => { +interface Props extends Omit<EuiDualRangeProps, 'value' | 'onChange' | 'min' | 'max'> { + minSize: number; + maxSize: number; + onChange: ({ maxSize, minSize }: { maxSize: number; minSize: number }) => void; +} + +export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }: Props) { + const onSizeChange = ([min, max]: [string, string]) => { onChange({ minSize: Math.max(MIN_SIZE, parseInt(min, 10)), maxSize: Math.min(MAX_SIZE, parseInt(max, 10)), @@ -37,9 +43,3 @@ export function SizeRangeSelector({ minSize, maxSize, onChange, ...rest }) { /> ); } - -SizeRangeSelector.propTypes = { - minSize: PropTypes.number.isRequired, - maxSize: PropTypes.number.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.js b/x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.tsx similarity index 69% rename from x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.tsx index 64ef17b67ed13..a3a7ad7e50bda 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/size/static_size_form.tsx @@ -5,13 +5,23 @@ * 2.0. */ -import React from 'react'; -import { ValidatedRange } from '../../../../../components/validated_range'; +import React, { ReactNode } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +// @ts-expect-error +import { ValidatedRange } from '../../../../../components/validated_range'; +import { SizeStaticOptions } from '../../../../../../common/descriptor_types'; +import { VECTOR_STYLES } from '../../../../../../common/constants'; +import { StaticSizeProperty } from '../../properties/static_size_property'; + +interface Props { + onStaticStyleChange: (propertyName: VECTOR_STYLES, options: SizeStaticOptions) => void; + staticDynamicSelect?: ReactNode; + styleProperty: StaticSizeProperty; +} -export function StaticSizeForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) { - const onSizeChange = (size) => { +export function StaticSizeForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }: Props) { + const onSizeChange = (size: number) => { onStaticStyleChange(styleProperty.getStyleName(), { size }); }; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx index b34ff87618f33..7c4aac0f32e25 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx @@ -8,19 +8,19 @@ import React from 'react'; import { Props, StylePropEditor } from '../style_prop_editor'; -// @ts-expect-error import { DynamicSizeForm } from './dynamic_size_form'; -// @ts-expect-error import { StaticSizeForm } from './static_size_form'; import { SizeDynamicOptions, SizeStaticOptions } from '../../../../../../common/descriptor_types'; +import { DynamicSizeProperty } from '../../properties/dynamic_size_property'; +import { StaticSizeProperty } from '../../properties/static_size_property'; type SizeEditorProps = Omit<Props<SizeStaticOptions, SizeDynamicOptions>, 'children'>; export function VectorStyleSizeEditor(props: SizeEditorProps) { const sizeForm = props.styleProperty.isDynamic() ? ( - <DynamicSizeForm {...props} /> + <DynamicSizeForm {...props} styleProperty={props.styleProperty as DynamicSizeProperty} /> ) : ( - <StaticSizeForm {...props} /> + <StaticSizeForm {...props} styleProperty={props.styleProperty as StaticSizeProperty} /> ); return <StylePropEditor {...props}>{sizeForm}</StylePropEditor>; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx index cbf5a4fe868e7..091c4c4e36b2e 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.tsx @@ -142,6 +142,7 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio } _getOrdinalColorMbExpression() { + const invert = this._options.invert === undefined ? false : this._options.invert; const targetName = this.getMbFieldName(); if (this._options.useCustomColorRamp) { if (!this._options.customColorRamp || !this._options.customColorRamp.length) { @@ -177,7 +178,8 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio const colorStops = getPercentilesMbColorRampStops( this._options.color ? this._options.color : null, - percentilesFieldMeta + percentilesFieldMeta, + invert ); if (!colorStops) { return null; @@ -205,7 +207,8 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio const colorStops = getOrdinalMbColorRampStops( this._options.color ? this._options.color : null, rangeFieldMeta.min, - rangeFieldMeta.max + rangeFieldMeta.max, + invert ); if (!colorStops) { return null; @@ -350,6 +353,7 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio } _getOrdinalBreaks(symbolId?: string, svg?: string): Break[] { + const invert = this._options.invert === undefined ? false : this._options.invert; let colorStops: Array<number | string> | null = null; let getValuePrefix: ((i: number, isNext: boolean) => string) | null = null; if (this._options.useCustomColorRamp) { @@ -364,7 +368,8 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio } colorStops = getPercentilesMbColorRampStops( this._options.color ? this._options.color : null, - percentilesFieldMeta + percentilesFieldMeta, + invert ); getValuePrefix = function (i: number, isNext: boolean) { const percentile = isNext @@ -379,7 +384,9 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio return []; } if (rangeFieldMeta.delta === 0) { - const colors = getColorPalette(this._options.color); + const colors = invert + ? getColorPalette(this._options.color).reverse() + : getColorPalette(this._options.color); // map to last color. return [ { @@ -393,7 +400,8 @@ export class DynamicColorProperty extends DynamicStyleProperty<ColorDynamicOptio colorStops = getOrdinalMbColorRampStops( this._options.color ? this._options.color : null, rangeFieldMeta.min, - rangeFieldMeta.max + rangeFieldMeta.max, + invert ); } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/__snapshots__/dynamic_size_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/__snapshots__/dynamic_size_property.test.tsx.snap index 218f3de142be5..b1fc94c4475ce 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/__snapshots__/dynamic_size_property.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/__snapshots__/dynamic_size_property.test.tsx.snap @@ -228,6 +228,7 @@ exports[`renderLegendDetailRow Should render line width simple range 1`] = ` </React.Fragment> </EuiFlexGroup> } + invert={false} maxLabel="100_format" minLabel="0_format" propertyLabel="Border width" diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx index 83ac50c7b4eaa..13d93dffaaec0 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx @@ -112,17 +112,25 @@ export class DynamicSizeProperty extends DynamicStyleProperty<SizeDynamicOptions this.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._isSymbolizedAsIcon ? this._options.minSize / HALF_MAKI_ICON_SIZE : this._options.minSize; - const stops = - rangeFieldMeta.min === rangeFieldMeta.max - ? [maxValueStopInput, maxRangeStopOutput] + const invert = this._options.invert === undefined ? false : this._options.invert; + function getStopsWithoutRange() { + return invert + ? [maxValueStopInput, minRangeStopOutput] + : [maxValueStopInput, maxRangeStopOutput]; + } + function getStops() { + return invert + ? [minValueStopInput, maxRangeStopOutput, maxValueStopInput, minRangeStopOutput] : [minValueStopInput, minRangeStopOutput, maxValueStopInput, maxRangeStopOutput]; + } + const stops = rangeFieldMeta.min === rangeFieldMeta.max ? getStopsWithoutRange() : getStops(); const valueExpression = makeMbClampedNumberExpression({ lookupFunction: this.getMbLookupFunction(), maxValue: rangeFieldMeta.max, minValue: rangeFieldMeta.min, fieldName: this.getMbFieldName(), - fallback: rangeFieldMeta.min, + fallback: invert ? rangeFieldMeta.max : rangeFieldMeta.min, }); const valueShiftExpression = rangeFieldMeta.min < 1 ? ['+', valueExpression, valueShift] : valueExpression; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index cc16e408bf7bb..865ad53ebe3da 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -13,12 +13,17 @@ import { Action, ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; import { maplibregl } from '@kbn/mapbox-gl'; import type { Map as MapboxMap, MapOptions, MapMouseEvent } from '@kbn/mapbox-gl'; import { ResizeChecker } from '@kbn/kibana-utils-plugin/public'; +import { METRIC_TYPE } from '@kbn/analytics'; import { DrawFilterControl } from './draw_control/draw_filter_control'; import { ScaleControl } from './scale_control'; import { TooltipControl } from './tooltip_control'; import { clampToLatBounds, clampToLonBounds } from '../../../common/elasticsearch_util'; import { getInitialView } from './get_initial_view'; -import { getPreserveDrawingBuffer, isScreenshotMode } from '../../kibana_services'; +import { + getPreserveDrawingBuffer, + getUsageCollection, + isScreenshotMode, +} from '../../kibana_services'; import { ILayer } from '../../classes/layers/layer'; import { CustomIcon, @@ -28,6 +33,7 @@ import { Timeslice, } from '../../../common/descriptor_types'; import { + APP_ID, CUSTOM_ICON_SIZE, DECIMAL_DEGREES_PRECISION, MAKI_ICON_SIZE, @@ -149,6 +155,7 @@ export class MbMap extends Component<Props, State> { } async _createMbMapInstance(initialView: MapCenterAndZoom | null): Promise<MapboxMap> { + this._reportUsage(); return new Promise((resolve) => { const mbStyle = { version: 8 as 8, @@ -270,6 +277,24 @@ export class MbMap extends Component<Props, State> { }); } + _reportUsage() { + const usageCollector = getUsageCollection(); + if (!usageCollector) return; + + const webglSupport = maplibregl.supported(); + + usageCollector.reportUiCounter( + APP_ID, + METRIC_TYPE.LOADED, + webglSupport ? 'gl_webglSupported' : 'gl_webglNotSupported' + ); + + // Report low system performance or no hardware GPU + if (webglSupport && !maplibregl.supported({ failIfMajorPerformanceCaveat: true })) { + usageCollector.reportUiCounter(APP_ID, METRIC_TYPE.LOADED, 'gl_majorPerformanceCaveat'); + } + } + async _loadMakiSprites(mbMap: MapboxMap) { if (this._isMounted) { // Math.floor rounds values < 1 to 0. This occurs when browser is zoomed out diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/_layer_toc.scss b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/_layer_toc.scss index 868c120c31691..a3a03097acb06 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/_layer_toc.scss +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/_layer_toc.scss @@ -6,6 +6,10 @@ cursor: alias !important; } +.mapLayerToc-droppable-isCombining { + background-color: $euiColorEmptyShade !important; +} + .mapLayerToc-droppable-isDragging * { cursor: ns-resize !important; } \ No newline at end of file diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.tsx index f152d1686b3bd..54a5d13e7702d 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/layer_toc.tsx @@ -244,6 +244,10 @@ export class LayerTOC extends Component<Props> { dragHandleProps={draggableProvided.dragHandleProps} isDragging={draggableSnapshot.isDragging} isDraggingOver={droppableSnapshot.isDraggingOver} + isCombineLayer={ + this.state.combineLayer !== null && + this.state.combineLayer.getId() === layer.getId() + } /> ); }} diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/_toc_entry.scss b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/_toc_entry.scss index 094d116b78623..959176547dfb2 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/_toc_entry.scss +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/_toc_entry.scss @@ -62,6 +62,11 @@ pointer-events: none !important; } +.mapTocEntry-isCombineLayer { + transition: background-color $euiAnimSpeedExtraSlow ease; + background-color: transparentize($euiColorSuccess, .75); +} + .mapTocEntry-isSelected { background-color: tintOrShade($euiColorLightShade, 60%, 20%); } diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx index 72eb38f07257e..012c1e97ad528 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry.tsx @@ -49,6 +49,7 @@ export interface OwnProps { dragHandleProps?: DraggableProvidedDragHandleProps; isDragging?: boolean; isDraggingOver?: boolean; + isCombineLayer?: boolean; } type Props = ReduxStateProps & ReduxDispatchProps & OwnProps; @@ -314,6 +315,7 @@ export class TOCEntry extends Component<Props, State> { const classes = classNames('mapTocEntry', { 'mapTocEntry-isDragging': this.props.isDragging, 'mapTocEntry-isDraggingOver': this.props.isDraggingOver, + 'mapTocEntry-isCombineLayer': this.props.isCombineLayer, 'mapTocEntry-isSelected': this.props.layer.isPreviewLayer() || (this.props.selectedLayer && this.props.selectedLayer.getId() === this.props.layer.getId()), diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index a1a8a19674a81..0e4ee6d913e32 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -31,7 +31,7 @@ export const getIsCloud = () => isCloudEnabled; export const getIndexNameFormComponent = () => pluginsStart.fileUpload.IndexNameFormComponent; export const getFileUploadComponent = () => pluginsStart.fileUpload.FileUploadComponent; -export const getIndexPatternService = () => pluginsStart.data.indexPatterns; +export const getIndexPatternService = () => pluginsStart.data.dataViews; export const getAutocompleteService = () => pluginsStart.unifiedSearch.autocomplete; export const getInspector = () => pluginsStart.inspector; export const getFileUpload = () => pluginsStart.fileUpload; diff --git a/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts index 55983a54a61a3..fb04e98601b19 100644 --- a/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts +++ b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts @@ -63,7 +63,7 @@ export const visualizeGeoFieldAction = createAction<VisualizeFieldContext>({ }); const getMapsLink = async (context: VisualizeFieldContext) => { - const indexPattern = await getIndexPatternService().get(context.dataViewSpec.id!); + const dataView = await getIndexPatternService().get(context.dataViewSpec.id!); // create initial layer descriptor const hasTooltips = context?.contextualFields?.length && context?.contextualFields[0] !== '_source'; @@ -76,7 +76,7 @@ const getMapsLink = async (context: VisualizeFieldContext) => { id: uuid(), type: SOURCE_TYPES.ES_SEARCH, tooltipProperties: hasTooltips ? context.contextualFields : [], - label: indexPattern.title, + label: dataView.getIndexPattern(), indexPatternId: context.dataViewSpec.id, geoField: context.fieldName, scalingType: SCALING_TYPES.MVT, diff --git a/x-pack/plugins/ml/common/types/anomalies.ts b/x-pack/plugins/ml/common/types/anomalies.ts index 9b6218e8c3f34..7fd71a81dfa88 100644 --- a/x-pack/plugins/ml/common/types/anomalies.ts +++ b/x-pack/plugins/ml/common/types/anomalies.ts @@ -183,6 +183,52 @@ export interface AnomalyRecordDoc { * purely single bucket and +5.0 means the anomaly is purely multi bucket. */ multi_bucket_impact?: number; + + /** + * An explanation for the anomaly score + */ + anomaly_score_explanation?: { + /** + * Type of the detected anomaly: spike or dip. + */ + anomaly_type?: 'dip' | 'spike'; + /** + * Length of the detected anomaly in the number of buckets. + */ + anomaly_length?: number; + /** + * Impact of the deviation between actual and typical in the current bucket. + */ + single_bucket_impact?: number; + /** + * Impact of the deviation between actual and typical in the past 12 buckets. + */ + multi_bucket_impact?: number; + /** + * Impact of the statistical properties of the detected anomalous interval. + */ + anomaly_characteristics_impact?: number; + /** + * Lower bound of the 95% confidence interval. + */ + lower_confidence_bound?: number; + /** + * Typical (expected) value for this bucket. + */ + typical_value?: number; + /** + * Upper bound of the 95% confidence interval. + */ + upper_confidence_bound?: number; + /** + * Indicates a reduction of anomaly score for the bucket with large confidence intervals. + */ + high_variance_penalty?: boolean; + /** + * Indicates a reduction of anomaly score if the bucket contains fewer samples than historically expected. + */ + incomplete_bucket_penalty?: boolean; + }; } /** @@ -283,6 +329,21 @@ export interface AnomaliesTableRecord { * Returns true if the anomaly record represented by the table row can be shown in the maps plugin */ isGeoRecord?: boolean; + + /** + * Returns true if the job has the model plot enabled + */ + modelPlotEnabled: boolean; +} + +/** + * Customized version of AnomaliesTableRecord which inserts the detector description + * and rules length. + * Used by the AnomaliesTable component + */ +export interface AnomaliesTableRecordExtended extends AnomaliesTableRecord { + detector: string; + rulesLength?: number; } export type PartitionFieldsType = typeof PARTITION_FIELDS[number]; diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss b/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss index 813fbdcbe6f1f..06dfacf63a213 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/_anomalies_table.scss @@ -91,28 +91,6 @@ } .ml-anomalies-table-details { - padding: $euiSizeXS $euiSizeXL; - max-height: 1000px; - overflow-y: auto; - - .anomaly-description-list { - - .euiDescriptionList__title { - margin-top: 0; - flex-basis: 15%; - font-size: inherit; - line-height: 1.5rem; - @include euiTextTruncate; - } - - .euiDescriptionList__description { - margin-top: 0; - flex-basis: 85%; - font-size: inherit; - line-height: 1.5rem; - } - - } + padding: $euiSizeM; } - } diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js index 9becc0dc75d5d..78c2c705c29e9 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js @@ -94,6 +94,8 @@ export class AnomaliesTableInternal extends Component { } } + const job = this.props.selectedJobs.find(({ id }) => id === item.jobId); + itemIdToExpandedRowMap[item.rowId] = ( <AnomalyDetails tabIndex={tab} @@ -104,6 +106,7 @@ export class AnomaliesTableInternal extends Component { filter={this.props.filter} influencerFilter={this.props.influencerFilter} influencersLimit={INFLUENCERS_LIMIT} + job={job} /> ); } @@ -277,4 +280,5 @@ AnomaliesTableInternal.propTypes = { tableState: PropTypes.object.isRequired, updateTableState: PropTypes.func.isRequired, sourceIndicesWithGeoFields: PropTypes.object.isRequired, + selectedJobs: PropTypes.array.isRequired, }; diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_constants.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_constants.ts similarity index 100% rename from x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_constants.js rename to x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table_constants.ts diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js deleted file mode 100644 index a4f8ab231d086..0000000000000 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.js +++ /dev/null @@ -1,394 +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. - */ - -/* - * React component for displaying details of an anomaly in the expanded row section - * of the anomalies table. - */ - -import PropTypes from 'prop-types'; -import React, { Component, Fragment } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { - EuiDescriptionList, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiIconTip, - EuiLink, - EuiSpacer, - EuiTabbedContent, - EuiText, -} from '@elastic/eui'; - -import { getSeverity } from '../../../../common/util/anomaly_utils'; -import { MAX_CHARS } from './anomalies_table_constants'; - -import { getDetailsItems, getInfluencersItems } from './anomaly_details_utils'; - -export class AnomalyDetails extends Component { - static propTypes = { - anomaly: PropTypes.object.isRequired, - examples: PropTypes.array, - definition: PropTypes.object, - isAggregatedData: PropTypes.bool, - filter: PropTypes.func, - influencersLimit: PropTypes.number, - influencerFilter: PropTypes.func, - tabIndex: PropTypes.number.isRequired, - }; - - constructor(props) { - super(props); - this.state = { - showAllInfluencers: false, - }; - - if (this.props.examples !== undefined && this.props.examples.length > 0) { - this.tabs = [ - { - id: 'Details', - name: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.detailsTitle', { - defaultMessage: 'Details', - }), - content: ( - <Fragment> - <div - className="ml-anomalies-table-details" - data-test-subj="mlAnomaliesListRowDetails" - > - {this.renderDescription()} - <EuiSpacer size="m" /> - {this.renderDetails()} - {this.renderInfluencers()} - </div> - </Fragment> - ), - }, - { - id: 'category-examples', - name: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.categoryExamplesTitle', { - defaultMessage: 'Category examples', - }), - content: <Fragment>{this.renderCategoryExamples()}</Fragment>, - }, - ]; - } - } - - toggleAllInfluencers() { - this.setState({ showAllInfluencers: !this.state.showAllInfluencers }); - } - - renderCategoryExamples() { - const { examples, definition } = this.props; - - return ( - <EuiFlexGroup - direction="column" - justifyContent="center" - gutterSize="m" - className="mlAnomalyCategoryExamples" - > - {definition !== undefined && definition.terms && ( - <Fragment> - <EuiFlexItem key={`example-terms`}> - <EuiText size="xs"> - <h4 className="mlAnomalyCategoryExamples__header"> - {i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.termsTitle', { - defaultMessage: 'Terms', - })} - </h4> -   - <EuiIconTip - aria-label={i18n.translate( - 'xpack.ml.anomaliesTable.anomalyDetails.termsDescriptionAriaLabel', - { - defaultMessage: 'Description', - } - )} - type="questionInCircle" - color="subdued" - size="s" - content={ - <FormattedMessage - id="xpack.ml.anomaliesTable.anomalyDetails.termsDescriptionTooltip" - defaultMessage="A space separated list of the common tokens that are matched in values of the category - (may have been truncated to a max character limit of {maxChars})" - values={{ maxChars: MAX_CHARS }} - /> - } - /> - </EuiText> - <EuiText size="xs">{definition.terms}</EuiText> - </EuiFlexItem> - <EuiSpacer size="xs" /> - </Fragment> - )} - {definition !== undefined && definition.regex && ( - <Fragment> - <EuiFlexItem key={`example-regex`}> - <EuiText size="xs"> - <h4 className="mlAnomalyCategoryExamples__header"> - {i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.regexTitle', { - defaultMessage: 'Regex', - })} - </h4> -   - <EuiIconTip - aria-label={i18n.translate( - 'xpack.ml.anomaliesTable.anomalyDetails.regexDescriptionAriaLabel', - { - defaultMessage: 'Description', - } - )} - type="questionInCircle" - color="subdued" - size="s" - content={ - <FormattedMessage - id="xpack.ml.anomaliesTable.anomalyDetails.regexDescriptionTooltip" - defaultMessage="The regular expression that is used to search for values that match the category - (may have been truncated to a max character limit of {maxChars})" - values={{ maxChars: MAX_CHARS }} - /> - } - /> - </EuiText> - <EuiText size="xs">{definition.regex}</EuiText> - </EuiFlexItem> - <EuiSpacer size="xs" /> - </Fragment> - )} - - {examples.map((example, i) => { - return ( - <EuiFlexItem key={`example${i}`}> - {i === 0 && definition !== undefined && ( - <EuiText size="s"> - <h4> - {i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.examplesTitle', { - defaultMessage: 'Examples', - })} - </h4> - </EuiText> - )} - <span className="mlAnomalyCategoryExamples__item">{example}</span> - </EuiFlexItem> - ); - })} - </EuiFlexGroup> - ); - } - - renderDescription() { - const anomaly = this.props.anomaly; - const source = anomaly.source; - - let anomalyDescription = i18n.translate( - 'xpack.ml.anomaliesTable.anomalyDetails.anomalyInLabel', - { - defaultMessage: '{anomalySeverity} anomaly in {anomalyDetector}', - values: { - anomalySeverity: getSeverity(anomaly.severity).label, - anomalyDetector: anomaly.detector, - }, - } - ); - if (anomaly.entityName !== undefined) { - anomalyDescription += i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.foundForLabel', { - defaultMessage: ' found for {anomalyEntityName} {anomalyEntityValue}', - values: { - anomalyEntityName: anomaly.entityName, - anomalyEntityValue: anomaly.entityValue, - }, - }); - } - - if ( - source.partition_field_name !== undefined && - source.partition_field_name !== anomaly.entityName - ) { - anomalyDescription += i18n.translate( - 'xpack.ml.anomaliesTable.anomalyDetails.detectedInLabel', - { - defaultMessage: ' detected in {sourcePartitionFieldName} {sourcePartitionFieldValue}', - values: { - sourcePartitionFieldName: source.partition_field_name, - sourcePartitionFieldValue: source.partition_field_value, - }, - } - ); - } - - // Check for a correlatedByFieldValue in the source which will be present for multivariate analyses - // where the record is anomalous due to relationship with another 'by' field value. - let mvDescription = undefined; - if (source.correlated_by_field_value !== undefined) { - mvDescription = i18n.translate( - 'xpack.ml.anomaliesTable.anomalyDetails.multivariateDescription', - { - defaultMessage: - 'multivariate correlations found in {sourceByFieldName}; ' + - '{sourceByFieldValue} is considered anomalous given {sourceCorrelatedByFieldValue}', - values: { - sourceByFieldName: source.by_field_name, - sourceByFieldValue: source.by_field_value, - sourceCorrelatedByFieldValue: source.correlated_by_field_value, - }, - } - ); - } - - return ( - <React.Fragment> - <EuiText size="xs"> - <h4> - <FormattedMessage - id="xpack.ml.anomaliesTable.anomalyDetails.descriptionTitle" - defaultMessage="Description" - /> - </h4> - {anomalyDescription} - </EuiText> - {mvDescription !== undefined && <EuiText size="xs">{mvDescription}</EuiText>} - </React.Fragment> - ); - } - - renderDetails() { - const detailItems = getDetailsItems(this.props.anomaly, this.props.filter); - const isInterimResult = this.props.anomaly.source?.is_interim ?? false; - return ( - <React.Fragment> - <EuiText size="xs"> - {this.props.isAggregatedData === true ? ( - <h4> - <FormattedMessage - id="xpack.ml.anomaliesTable.anomalyDetails.detailsOnHighestSeverityAnomalyTitle" - defaultMessage="Details on highest severity anomaly" - /> - </h4> - ) : ( - <h4> - <FormattedMessage - id="xpack.ml.anomaliesTable.anomalyDetails.anomalyDetailsTitle" - defaultMessage="Anomaly details" - /> - </h4> - )} - {isInterimResult === true && ( - <React.Fragment> - <EuiIcon type="alert" /> - <span className="interim-result"> - <FormattedMessage - id="xpack.ml.anomaliesTable.anomalyDetails.interimResultLabel" - defaultMessage="Interim result" - /> - </span> - </React.Fragment> - )} - </EuiText> - <EuiDescriptionList - type="column" - listItems={detailItems} - className="anomaly-description-list" - /> - </React.Fragment> - ); - } - - renderInfluencers() { - const anomalyInfluencers = this.props.anomaly.influencers; - let listItems = []; - let othersCount = 0; - let numToDisplay = 0; - if (anomalyInfluencers !== undefined) { - numToDisplay = - this.state.showAllInfluencers === true - ? anomalyInfluencers.length - : Math.min(this.props.influencersLimit, anomalyInfluencers.length); - othersCount = Math.max(anomalyInfluencers.length - numToDisplay, 0); - - if (othersCount === 1) { - // Display the 1 extra influencer as displaying "and 1 more" would also take up a line. - numToDisplay++; - othersCount = 0; - } - - listItems = getInfluencersItems( - anomalyInfluencers, - this.props.influencerFilter, - numToDisplay - ); - } - - if (listItems.length > 0) { - return ( - <React.Fragment> - <EuiSpacer size="m" /> - <EuiText size="xs"> - <h4> - <FormattedMessage - id="xpack.ml.anomaliesTable.anomalyDetails.influencersTitle" - defaultMessage="Influencers" - /> - </h4> - </EuiText> - <EuiDescriptionList - type="column" - listItems={listItems} - className="anomaly-description-list" - /> - {othersCount > 0 && ( - <EuiLink onClick={() => this.toggleAllInfluencers()}> - <FormattedMessage - id="xpack.ml.anomaliesTable.anomalyDetails.anomalyDescriptionListMoreLinkText" - defaultMessage="and {othersCount} more" - values={{ othersCount }} - /> - </EuiLink> - )} - {numToDisplay > this.props.influencersLimit + 1 && ( - <EuiLink onClick={() => this.toggleAllInfluencers()}> - <FormattedMessage - id="xpack.ml.anomaliesTable.anomalyDetails.anomalyDescriptionShowLessLinkText" - defaultMessage="Show less" - /> - </EuiLink> - )} - </React.Fragment> - ); - } - } - - render() { - const { tabIndex } = this.props; - - if (this.tabs !== undefined) { - return ( - <EuiTabbedContent - tabs={this.tabs} - size="s" - initialSelectedTab={this.tabs[tabIndex]} - onTabClick={() => {}} - /> - ); - } else { - return ( - <div className="ml-anomalies-table-details" data-test-subj="mlAnomaliesListRowDetails"> - {this.renderDescription()} - <EuiSpacer size="m" /> - {this.renderDetails()} - {this.renderInfluencers()} - </div> - ); - } - } -} diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.test.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.test.js index 67f6a9b12dfa1..193c43631013a 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.test.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { AnomalyDetails } from './anomaly_details'; const props = { @@ -52,23 +52,25 @@ const props = { influencersLimit: 5, isAggregatedData: true, tabIndex: 0, + job: { + id: 'it-ops-count-by-mlcategory-one', + selected: false, + bucketSpanSeconds: 900, + isSingleMetricViewerJob: false, + sourceIndices: [''], + modelPlotEnabled: false, + }, }; describe('AnomalyDetails', () => { - test('Renders with anomaly details tab selected by default', () => { - const wrapper = shallowWithIntl(<AnomalyDetails {...props} />); - - expect(wrapper.prop('tabs').length).toBe(2); - expect(wrapper.prop('initialSelectedTab').id).toBe('Details'); - }); - - test('Renders with category tab selected when index set to 1', () => { + test('Renders two tabs', () => { const categoryTabProps = { ...props, tabIndex: 1, }; - const wrapper = shallowWithIntl(<AnomalyDetails {...categoryTabProps} />); - expect(wrapper.prop('initialSelectedTab').id).toBe('category-examples'); + const wrapper = mountWithIntl(<AnomalyDetails {...categoryTabProps} />); + expect(wrapper.containsMatchingElement(<span>Details</span>)).toBe(true); + expect(wrapper.containsMatchingElement(<span>Category examples</span>)).toBe(true); }); test('Renders with terms and regex when definition prop is not undefined', () => { diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.tsx b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.tsx new file mode 100644 index 0000000000000..56ff831b57992 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.tsx @@ -0,0 +1,457 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * React component for displaying details of an anomaly in the expanded row section + * of the anomalies table. + */ + +import React, { FC, useState, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { capitalize } from 'lodash'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiIconTip, + EuiLink, + EuiSpacer, + EuiTabbedContent, + EuiText, + useEuiTheme, +} from '@elastic/eui'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; + +import { getSeverity } from '../../../../common/util/anomaly_utils'; +import { MAX_CHARS } from './anomalies_table_constants'; +import type { CategoryDefinition } from '../../services/ml_api_service/results'; +import { AnomaliesTableRecordExtended } from '../../../../common/types/anomalies'; +import { EntityCellFilter } from '../entity_cell'; +import { ExplorerJob } from '../../explorer/explorer_utils'; + +import { + getInfluencersItems, + AnomalyExplanationDetails, + DetailsItems, +} from './anomaly_details_utils'; + +interface Props { + anomaly: AnomaliesTableRecordExtended; + examples: string[]; + definition: CategoryDefinition; + isAggregatedData: boolean; + filter: EntityCellFilter; + influencersLimit: number; + influencerFilter: EntityCellFilter; + tabIndex: number; + job: ExplorerJob; +} + +export const AnomalyDetails: FC<Props> = ({ + anomaly, + examples, + definition, + isAggregatedData, + filter, + influencersLimit, + influencerFilter, + tabIndex, + job, +}) => { + if (examples !== undefined && examples.length > 0) { + const tabs = [ + { + id: 'Details', + name: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.detailsTitle', { + defaultMessage: 'Details', + }), + content: ( + <Contents + anomaly={anomaly} + filter={filter} + influencerFilter={influencerFilter} + influencersLimit={influencersLimit} + isAggregatedData={isAggregatedData} + job={job} + /> + ), + }, + { + id: 'category-examples', + name: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.categoryExamplesTitle', { + defaultMessage: 'Category examples', + }), + content: <CategoryExamples examples={examples} definition={definition} />, + }, + ]; + + return ( + <EuiFlexGroup> + <EuiFlexItem> + <EuiTabbedContent + tabs={tabs} + size="s" + initialSelectedTab={tabs[tabIndex]} + onTabClick={() => {}} + /> + </EuiFlexItem> + </EuiFlexGroup> + ); + } + + return ( + <Contents + anomaly={anomaly} + filter={filter} + influencerFilter={influencerFilter} + influencersLimit={influencersLimit} + isAggregatedData={isAggregatedData} + job={job} + /> + ); +}; + +const Contents: FC<{ + anomaly: AnomaliesTableRecordExtended; + isAggregatedData: boolean; + filter: EntityCellFilter; + influencersLimit: number; + influencerFilter: EntityCellFilter; + job: ExplorerJob; +}> = ({ anomaly, isAggregatedData, filter, influencersLimit, influencerFilter, job }) => { + const { + euiTheme: { colors }, + } = useEuiTheme(); + + const dividerStyle = useMemo(() => { + return isPopulatedObject(anomaly.source.anomaly_score_explanation) + ? { borderRight: `1px solid ${colors.lightShade}` } + : {}; + }, [colors, anomaly]); + + return ( + <EuiFlexGroup> + <EuiFlexItem> + <div className="ml-anomalies-table-details" data-test-subj="mlAnomaliesListRowDetails"> + <Description anomaly={anomaly} /> + <EuiSpacer size="m" /> + + <EuiFlexGroup gutterSize="l"> + <EuiFlexItem css={dividerStyle}> + <Details + anomaly={anomaly} + isAggregatedData={isAggregatedData} + filter={filter} + job={job} + /> + <Influencers + anomaly={anomaly} + influencerFilter={influencerFilter} + influencersLimit={influencersLimit} + /> + </EuiFlexItem> + <EuiFlexItem> + <AnomalyExplanationDetails anomaly={anomaly} /> + </EuiFlexItem> + </EuiFlexGroup> + </div> + </EuiFlexItem> + </EuiFlexGroup> + ); +}; + +const Description: FC<{ anomaly: AnomaliesTableRecordExtended }> = ({ anomaly }) => { + const source = anomaly.source; + + let anomalyDescription = i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.anomalyInLabel', { + defaultMessage: '{anomalySeverity} anomaly in {anomalyDetector}', + values: { + anomalySeverity: capitalize(getSeverity(anomaly.severity).label), + anomalyDetector: anomaly.detector, + }, + }); + if (anomaly.entityName !== undefined) { + anomalyDescription += i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.foundForLabel', { + defaultMessage: ' found for {anomalyEntityName} {anomalyEntityValue}', + values: { + anomalyEntityName: anomaly.entityName, + anomalyEntityValue: anomaly.entityValue, + }, + }); + } + + if ( + source.partition_field_name !== undefined && + source.partition_field_name !== anomaly.entityName + ) { + anomalyDescription += i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.detectedInLabel', { + defaultMessage: ' detected in {sourcePartitionFieldName} {sourcePartitionFieldValue}', + values: { + sourcePartitionFieldName: source.partition_field_name, + sourcePartitionFieldValue: source.partition_field_value, + }, + }); + } + + // Check for a correlatedByFieldValue in the source which will be present for multivariate analyses + // where the record is anomalous due to relationship with another 'by' field value. + let mvDescription; + if (source.correlated_by_field_value !== undefined) { + mvDescription = i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.multivariateDescription', + { + defaultMessage: + 'multivariate correlations found in {sourceByFieldName}; ' + + '{sourceByFieldValue} is considered anomalous given {sourceCorrelatedByFieldValue}', + values: { + sourceByFieldName: source.by_field_name, + sourceByFieldValue: source.by_field_value, + sourceCorrelatedByFieldValue: source.correlated_by_field_value, + }, + } + ); + } + + return ( + <> + <EuiText size="xs"> + <h4> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.descriptionTitle" + defaultMessage="Description" + /> + </h4> + {anomalyDescription} + </EuiText> + {mvDescription !== undefined && <EuiText size="xs">{mvDescription}</EuiText>} + </> + ); +}; + +const Details: FC<{ + anomaly: AnomaliesTableRecordExtended; + isAggregatedData: boolean; + filter: EntityCellFilter; + job: ExplorerJob; +}> = ({ anomaly, isAggregatedData, filter, job }) => { + const isInterimResult = anomaly.source?.is_interim ?? false; + return ( + <> + <EuiText size="xs"> + {isAggregatedData === true ? ( + <h4> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.detailsOnHighestSeverityAnomalyTitle" + defaultMessage="Details on highest severity anomaly" + /> + </h4> + ) : ( + <h4> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.anomalyDetailsTitle" + defaultMessage="Anomaly details" + /> + </h4> + )} + {isInterimResult === true && ( + <> + <EuiIcon type="alert" /> + <span className="interim-result"> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.interimResultLabel" + defaultMessage="Interim result" + /> + </span> + </> + )} + </EuiText> + + <EuiSpacer size="xs" /> + + <DetailsItems anomaly={anomaly} filter={filter} modelPlotEnabled={job.modelPlotEnabled} /> + </> + ); +}; + +const Influencers: FC<{ + anomaly: AnomaliesTableRecordExtended; + influencersLimit: number; + influencerFilter: EntityCellFilter; +}> = ({ anomaly, influencersLimit, influencerFilter }) => { + const [showAllInfluencers, setShowAllInfluencers] = useState(false); + const toggleAllInfluencers = setShowAllInfluencers.bind(null, (prev) => !prev); + + const anomalyInfluencers = anomaly.influencers; + let listItems: Array<{ title: string; description: React.ReactElement }> = []; + let othersCount = 0; + let numToDisplay = 0; + if (anomalyInfluencers !== undefined) { + numToDisplay = + showAllInfluencers === true + ? anomalyInfluencers.length + : Math.min(influencersLimit, anomalyInfluencers.length); + othersCount = Math.max(anomalyInfluencers.length - numToDisplay, 0); + + if (othersCount === 1) { + // Display the 1 extra influencer as displaying "and 1 more" would also take up a line. + numToDisplay++; + othersCount = 0; + } + + listItems = getInfluencersItems(anomalyInfluencers, influencerFilter, numToDisplay); + } + + if (listItems.length > 0) { + return ( + <> + <EuiSpacer size="m" /> + <EuiText size="xs"> + <h4> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.influencersTitle" + defaultMessage="Influencers" + /> + </h4> + </EuiText> + + {listItems.map(({ title, description }) => ( + <> + <EuiFlexGroup gutterSize="none"> + <EuiFlexItem style={{ width: '180px' }} grow={false}> + {title} + </EuiFlexItem> + <EuiFlexItem>{description}</EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="xs" /> + </> + ))} + {othersCount > 0 && ( + <EuiLink onClick={() => toggleAllInfluencers()}> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.anomalyDescriptionListMoreLinkText" + defaultMessage="and {othersCount} more" + values={{ othersCount }} + /> + </EuiLink> + )} + {numToDisplay > influencersLimit + 1 && ( + <EuiLink onClick={() => toggleAllInfluencers()}> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.anomalyDescriptionShowLessLinkText" + defaultMessage="Show less" + /> + </EuiLink> + )} + </> + ); + } + return null; +}; + +const CategoryExamples: FC<{ definition: CategoryDefinition; examples: string[] }> = ({ + definition, + examples, +}) => { + return ( + <EuiFlexGroup + direction="column" + justifyContent="center" + gutterSize="m" + className="mlAnomalyCategoryExamples" + > + {definition !== undefined && definition.terms && ( + <> + <EuiFlexItem key={`example-terms`}> + <EuiText size="xs"> + <h4 className="mlAnomalyCategoryExamples__header"> + {i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.termsTitle', { + defaultMessage: 'Terms', + })} + </h4> +   + <EuiIconTip + aria-label={i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.termsDescriptionAriaLabel', + { + defaultMessage: 'Description', + } + )} + type="questionInCircle" + color="subdued" + size="s" + content={ + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.termsDescriptionTooltip" + defaultMessage="A space separated list of the common tokens that are matched in values of the category + (may have been truncated to a max character limit of {maxChars})" + values={{ maxChars: MAX_CHARS }} + /> + } + /> + </EuiText> + <EuiText size="xs">{definition.terms}</EuiText> + </EuiFlexItem> + <EuiSpacer size="xs" /> + </> + )} + {definition !== undefined && definition.regex && ( + <> + <EuiFlexItem key={`example-regex`}> + <EuiText size="xs"> + <h4 className="mlAnomalyCategoryExamples__header"> + {i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.regexTitle', { + defaultMessage: 'Regex', + })} + </h4> +   + <EuiIconTip + aria-label={i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.regexDescriptionAriaLabel', + { + defaultMessage: 'Description', + } + )} + type="questionInCircle" + color="subdued" + size="s" + content={ + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.regexDescriptionTooltip" + defaultMessage="The regular expression that is used to search for values that match the category + (may have been truncated to a max character limit of {maxChars})" + values={{ maxChars: MAX_CHARS }} + /> + } + /> + </EuiText> + <EuiText size="xs">{definition.regex}</EuiText> + </EuiFlexItem> + <EuiSpacer size="xs" /> + </> + )} + + {examples.map((example, i) => { + return ( + <EuiFlexItem key={`example${i}`}> + {i === 0 && definition !== undefined && ( + <EuiText size="s"> + <h4> + {i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.examplesTitle', { + defaultMessage: 'Examples', + })} + </h4> + </EuiText> + )} + <span className="mlAnomalyCategoryExamples__item">{example}</span> + </EuiFlexItem> + ); + })} + </EuiFlexGroup> + ); +}; diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx index 89eedee36f344..17d479de1db8d 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx @@ -5,20 +5,28 @@ * 2.0. */ -import React from 'react'; +import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiToolTip, EuiIcon } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiToolTip, + EuiIcon, + EuiFlexGroup, + EuiFlexItem, + useEuiTheme, + EuiText, + EuiSpacer, +} from '@elastic/eui'; import { EntityCell, EntityCellFilter } from '../entity_cell'; import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils'; import { - getMultiBucketImpactLabel, showActualForFunction, showTypicalForFunction, } from '../../../../common/util/anomaly_utils'; -import { MULTI_BUCKET_IMPACT } from '../../../../common/constants/multi_bucket_impact'; -import { AnomaliesTableRecord } from '../../../../common/types/anomalies'; +import { AnomaliesTableRecord, MLAnomalyDoc } from '../../../../common/types/anomalies'; import { formatValue } from '../../formatters/format_value'; import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types'; +import { getSeverityColor } from '../../../../common/util/anomaly_utils'; const TIME_FIELD_NAME = 'timestamp'; @@ -54,7 +62,11 @@ export function getInfluencersItems( return items; } -export function getDetailsItems(anomaly: AnomaliesTableRecord, filter: EntityCellFilter) { +export const DetailsItems: FC<{ + anomaly: AnomaliesTableRecord; + filter: EntityCellFilter; + modelPlotEnabled: boolean; +}> = ({ anomaly, filter, modelPlotEnabled }) => { const source = anomaly.source; // TODO - when multivariate analyses are more common, @@ -172,6 +184,31 @@ export function getDetailsItems(anomaly: AnomaliesTableRecord, filter: EntityCel }), description: formatValue(anomaly.typical, source.function, undefined, source), }); + + if ( + modelPlotEnabled === false && + anomaly.source.anomaly_score_explanation?.lower_confidence_bound !== undefined && + anomaly.source.anomaly_score_explanation?.upper_confidence_bound !== undefined + ) { + items.push({ + title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.upperBoundsTitle', { + defaultMessage: 'Upper bounds', + }), + description: formatValue(anomaly.typical, source.function, undefined, source), + }); + + items.push({ + title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.lowerBoundsTitle', { + defaultMessage: 'Lower bounds', + }), + description: formatValue( + anomaly.source.anomaly_score_explanation?.lower_confidence_bound, + source.function, + undefined, + source + ), + }); + } } items.push({ @@ -181,18 +218,6 @@ export function getDetailsItems(anomaly: AnomaliesTableRecord, filter: EntityCel description: anomaly.jobId, }); - if ( - source.multi_bucket_impact !== undefined && - source.multi_bucket_impact >= MULTI_BUCKET_IMPACT.LOW - ) { - items.push({ - title: i18n.translate('xpack.ml.anomaliesTable.anomalyDetails.multiBucketImpactTitle', { - defaultMessage: 'Multi-bucket impact', - }), - description: getMultiBucketImpactLabel(source.multi_bucket_impact), - }); - } - items.push({ title: ( <EuiToolTip @@ -241,7 +266,7 @@ export function getDetailsItems(anomaly: AnomaliesTableRecord, filter: EntityCel defaultMessage: 'Probability', }), description: - // @ts-expect-error parseFloat accept take a number + // @ts-expect-error parseFloat accepts a number source.probability !== undefined ? Number.parseFloat(source.probability).toPrecision(3) : '', }); @@ -276,5 +301,422 @@ export function getDetailsItems(anomaly: AnomaliesTableRecord, filter: EntityCel }); } - return items; + return ( + <> + {items.map(({ title, description }) => ( + <> + <EuiFlexGroup gutterSize="none"> + <EuiFlexItem css={{ width: '180px' }} grow={false}> + {title} + </EuiFlexItem> + <EuiFlexItem>{description}</EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="xs" /> + </> + ))} + </> + ); +}; + +export const AnomalyExplanationDetails: FC<{ anomaly: AnomaliesTableRecord }> = ({ anomaly }) => { + const explanation = anomaly.source.anomaly_score_explanation; + if (explanation === undefined) { + return null; + } + const initialScore = Math.floor(1000 * anomaly.source.initial_record_score) / 1000; + const finalScore = Math.floor(1000 * anomaly.source.record_score) / 1000; + const scoreDifference = initialScore - finalScore; + const ACCEPTABLE_NORMALIZATION = 10; + + const yes = i18n.translate('xpack.ml.anomaliesTable.anomalyExplanationDetails.yes', { + defaultMessage: 'Yes', + }); + const no = i18n.translate('xpack.ml.anomaliesTable.anomalyExplanationDetails.no', { + defaultMessage: 'No', + }); + + const explanationDetails = []; + const anomalyType = getAnomalyType(explanation); + if (anomalyType !== null) { + explanationDetails.push({ + title: ( + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.anomalyType" + defaultMessage="Anomaly type" + /> + ), + description: <>{anomalyType}</>, + }); + } + + if (scoreDifference > ACCEPTABLE_NORMALIZATION) { + explanationDetails.push({ + title: ( + <EuiToolTip + position="left" + content={i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.recordScoreTooltip', + { + defaultMessage: + 'The initial record score has been reduced based on the analysis of subsequent data.', + } + )} + > + <span> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.recordScore" + defaultMessage="Record score reduction" + /> + <EuiIcon size="s" color="subdued" type="questionInCircle" className="eui-alignTop" /> + </span> + </EuiToolTip> + ), + description: ( + <EuiFlexGroup gutterSize="xs"> + <EuiFlexItem grow={false}> + <RecordScore score={initialScore} /> + </EuiFlexItem> + <EuiFlexItem grow={false}>{` -> `}</EuiFlexItem> + <EuiFlexItem grow={false}> + <RecordScore score={finalScore} /> + </EuiFlexItem> + </EuiFlexGroup> + ), + }); + } + + const impactDetails = []; + + if (explanation.anomaly_characteristics_impact !== undefined) { + impactDetails.push({ + title: ( + <EuiToolTip + position="left" + content={getImpactTooltip( + explanation.anomaly_characteristics_impact, + 'anomaly_characteristics' + )} + > + <span> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.anomalyCharacteristics" + defaultMessage="Anomaly characteristics impact" + /> + <EuiIcon size="s" color="subdued" type="questionInCircle" className="eui-alignTop" /> + </span> + </EuiToolTip> + ), + description: <ImpactVisual score={explanation.anomaly_characteristics_impact} />, + }); + } + + if (explanation.single_bucket_impact !== undefined) { + impactDetails.push({ + title: ( + <EuiToolTip + position="left" + content={getImpactTooltip(explanation.single_bucket_impact, 'single_bucket')} + > + <span> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.singleBucket" + defaultMessage="Single bucket impact" + /> + <EuiIcon size="s" color="subdued" type="questionInCircle" className="eui-alignTop" /> + </span> + </EuiToolTip> + ), + description: <ImpactVisual score={explanation.single_bucket_impact} />, + }); + } + if (explanation.multi_bucket_impact !== undefined) { + impactDetails.push({ + title: ( + <EuiToolTip + position="left" + content={getImpactTooltip(explanation.multi_bucket_impact, 'multi_bucket')} + > + <span> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.multiBucket" + defaultMessage="Multi bucket impact" + /> + <EuiIcon size="s" color="subdued" type="questionInCircle" className="eui-alignTop" /> + </span> + </EuiToolTip> + ), + description: <ImpactVisual score={explanation.multi_bucket_impact} />, + }); + } + if (explanation.high_variance_penalty !== undefined) { + impactDetails.push({ + title: ( + <EuiToolTip + position="left" + content={i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.highVarianceTooltip', + { + defaultMessage: + 'Indicates reduction of anomaly score for the bucket with large confidence intervals. If a bucket has large confidence intervals, the score is reduced.', + } + )} + > + <span> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.highVariance" + defaultMessage="High variance interval" + /> + <EuiIcon size="s" color="subdued" type="questionInCircle" className="eui-alignTop" /> + </span> + </EuiToolTip> + ), + description: explanation.high_variance_penalty ? yes : no, + }); + } + if (explanation.incomplete_bucket_penalty !== undefined) { + impactDetails.push({ + title: ( + <EuiToolTip + position="left" + content={i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.incompleteBucketTooltip', + { + defaultMessage: + 'If the bucket contains fewer samples than expected, the score is reduced. If the bucket contains fewer samples than expected, the score is reduced.', + } + )} + > + <span> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.incompleteBucket" + defaultMessage="Incomplete bucket" + /> + <EuiIcon size="s" color="subdued" type="questionInCircle" className="eui-alignTop" /> + </span> + </EuiToolTip> + ), + description: explanation.incomplete_bucket_penalty ? yes : no, + }); + } + + return ( + <div> + <EuiText size="xs"> + <h4> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationTitle" + defaultMessage="Anomaly explanation" + /> + </h4> + </EuiText> + <EuiSpacer size="s" /> + + {explanationDetails.map(({ title, description }) => ( + <> + <EuiFlexGroup gutterSize="none"> + <EuiFlexItem style={{ width: '220px' }} grow={false}> + {title} + </EuiFlexItem> + <EuiFlexItem>{description}</EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="xs" /> + </> + ))} + + <EuiSpacer size="s" /> + {impactDetails.length ? ( + <> + <EuiText size="xs"> + <h4> + <FormattedMessage + id="xpack.ml.anomaliesTable.anomalyDetails.impactOnScoreTitle" + defaultMessage="Impact on initial score" + /> + </h4> + </EuiText> + <EuiSpacer size="s" /> + + {impactDetails.map(({ title, description }) => ( + <> + <EuiFlexGroup gutterSize="none"> + <EuiFlexItem css={{ width: '220px' }} grow={false}> + {title} + </EuiFlexItem> + <EuiFlexItem>{description}</EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="xs" /> + </> + ))} + </> + ) : null} + </div> + ); +}; + +const RecordScore: FC<{ score: number }> = ({ score }) => { + return ( + <div + css={{ + borderBottom: '2px solid', + }} + style={{ + borderBottomColor: getSeverityColor(score), + }} + > + {score} + </div> + ); +}; + +function getAnomalyType(explanation: MLAnomalyDoc['anomaly_score_explanation']) { + if ( + explanation === undefined || + explanation.anomaly_length === undefined || + explanation.anomaly_type === undefined + ) { + return null; + } + + const dip = i18n.translate('xpack.ml.anomaliesTable.anomalyExplanationDetails.anomalyType.dip', { + defaultMessage: 'Dip over {anomalyLength, plural, one {# bucket} other {# buckets}}', + values: { anomalyLength: explanation.anomaly_length }, + }); + const spike = i18n.translate( + 'xpack.ml.anomaliesTable.anomalyExplanationDetails.anomalyType.spike', + { + defaultMessage: 'Spike over {anomalyLength, plural, one {# bucket} other {# buckets}}', + values: { anomalyLength: explanation.anomaly_length }, + } + ); + + return explanation.anomaly_type === 'dip' ? dip : spike; +} + +function getImpactValue(score: number) { + if (score < 2) return 1; + if (score < 4) return 2; + if (score < 7) return 3; + if (score < 12) return 4; + return 5; } + +const impactTooltips = { + anomaly_characteristics: { + low: i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.anomalyCharacteristicsTooltip.low', + { + defaultMessage: + 'Moderate impact from the duration and magnitude of the detected anomaly relative to the historical average.', + } + ), + medium: i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.anomalyCharacteristicsTooltip.medium', + { + defaultMessage: + 'Medium impact from the duration and magnitude of the detected anomaly relative to the historical average.', + } + ), + high: i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.anomalyCharacteristicsTooltip.high', + { + defaultMessage: + 'High impact from the duration and magnitude of the detected anomaly relative to the historical average.', + } + ), + }, + single_bucket: { + low: i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.singleBucketTooltip.low', + { + defaultMessage: + 'The difference between actual and typical values in this bucket has a moderate impact.', + } + ), + medium: i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.singleBucketTooltip.medium', + { + defaultMessage: + 'The difference between actual and typical values in this bucket has a significant impact.', + } + ), + high: i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.singleBucketTooltip.high', + { + defaultMessage: + 'The difference between actual and typical values in this bucket has a high impact.', + } + ), + }, + multi_bucket: { + low: i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.multiBucketTooltip.low', + { + defaultMessage: + 'The differences between actual and typical values in the past 12 buckets have a moderate impact.', + } + ), + medium: i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.multiBucketTooltip.medium', + { + defaultMessage: + 'The differences between actual and typical values in the past 12 buckets have a significant impact.', + } + ), + high: i18n.translate( + 'xpack.ml.anomaliesTable.anomalyDetails.anomalyExplanationDetails.multiBucketTooltip.high', + { + defaultMessage: + 'The differences between actual and typical values in the past 12 buckets have a high impact.', + } + ), + }, +}; + +function getImpactTooltip( + score: number, + type: 'anomaly_characteristics' | 'single_bucket' | 'multi_bucket' +) { + const value = getImpactValue(score); + + if (value < 3) { + return impactTooltips[type].low; + } + if (value > 3) { + return impactTooltips[type].high; + } + + return impactTooltips[type].medium; +} + +const ImpactVisual: FC<{ score: number }> = ({ score }) => { + const { + euiTheme: { colors }, + } = useEuiTheme(); + + const impact = getImpactValue(score); + const boxPx = '10px'; + const emptyBox = colors.lightShade; + const fullBox = colors.primary; + return ( + <EuiFlexGroup gutterSize="xs"> + {Array(5) + .fill(null) + .map((v, i) => { + return ( + <EuiFlexItem grow={false}> + <div + css={{ + height: boxPx, + width: boxPx, + borderRadius: '2px', + }} + style={{ + backgroundColor: impact > i ? fullBox : emptyBox, + }} + /> + </EuiFlexItem> + ); + })} + </EuiFlexGroup> + ); +}; diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx index 536cc26888a98..b794e6bbdf2bc 100644 --- a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx +++ b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx @@ -104,7 +104,7 @@ const MockedEuiSuperDatePicker = EuiSuperDatePicker as jest.MockedFunction< describe('Navigation Menu: <DatePickerWrapper />', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); MockedEuiSuperDatePicker.mockClear(); }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_destination_index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_destination_index.ts new file mode 100644 index 0000000000000..2058cce04850d --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_destination_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 { DataFrameAnalyticsConfig } from '../../../../common/types/data_frame_analytics'; + +export const getDestinationIndex = (jobConfig: DataFrameAnalyticsConfig | undefined) => + (Array.isArray(jobConfig?.dest.index) ? jobConfig?.dest.index[0] : jobConfig?.dest.index) ?? ''; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts index f47b5b66f4944..d7391f7dfa8b7 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/index.ts @@ -31,6 +31,7 @@ export { getDefaultFieldsFromJobCaps, sortExplorationResultsFields, MAX_COLUMNS export { getIndexData } from './get_index_data'; export { getIndexFields } from './get_index_fields'; +export { getDestinationIndex } from './get_destination_index'; export { getScatterplotMatrixLegendType } from './get_scatterplot_matrix_legend_type'; export { useResultsViewConfig } from './use_results_view_config'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts index 9f739bfb3d58c..df430142a3193 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts @@ -29,6 +29,7 @@ import { isClassificationAnalysis, isRegressionAnalysis, } from '../../../../common/util/analytics_utils'; +import { getDestinationIndex } from './get_destination_index'; export const useResultsViewConfig = (jobId: string) => { const mlContext = useMlContext(); @@ -95,9 +96,7 @@ export const useResultsViewConfig = (jobId: string) => { } try { - const destIndex = Array.isArray(jobConfigUpdate.dest.index) - ? jobConfigUpdate.dest.index[0] - : jobConfigUpdate.dest.index; + const destIndex = getDestinationIndex(jobConfigUpdate); const destDataViewId = (await getDataViewIdFromName(destIndex)) ?? destIndex; let dataView: DataView | undefined; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx index 17453dd87b0d0..482c214f884a3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx @@ -19,6 +19,7 @@ import { getScatterplotMatrixLegendType, useResultsViewConfig, DataFrameAnalyticsConfig, + getDestinationIndex, } from '../../../../common'; import { ResultsSearchQuery, ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics'; @@ -32,6 +33,7 @@ import { LoadingPanel } from '../loading_panel'; import { FeatureImportanceSummaryPanelProps } from '../total_feature_importance_summary/feature_importance_summary'; import { useExplorationUrlState } from '../../hooks/use_exploration_url_state'; import { ExplorationQueryBarProps } from '../exploration_query_bar/exploration_query_bar'; +import { IndexPatternPrompt } from '../index_pattern_prompt'; function getFilters(resultsField: string) { return { @@ -114,6 +116,8 @@ export const ExplorationPageWrapper: FC<Props> = ({ }; const resultsField = jobConfig?.dest.results_field ?? ''; + const destIndex = getDestinationIndex(jobConfig); + const scatterplotFieldOptions = useScatterplotFieldOptions( indexPattern, jobConfig?.analyzed_fields?.includes, @@ -131,7 +135,12 @@ export const ExplorationPageWrapper: FC<Props> = ({ color="danger" iconType="cross" > - <p>{indexPatternErrorMessage}</p> + <p> + {indexPatternErrorMessage} + {needsDestIndexPattern ? ( + <IndexPatternPrompt destIndex={destIndex} color="text" /> + ) : null} + </p> </EuiCallOut> </EuiPanel> ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/index_pattern_prompt.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/index_pattern_prompt.tsx index 1f9362d7e56ca..cd60be7290b96 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/index_pattern_prompt.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/index_pattern_prompt/index_pattern_prompt.tsx @@ -11,10 +11,11 @@ import { EuiLink, EuiText } from '@elastic/eui'; import { useMlKibana } from '../../../../../contexts/kibana'; interface Props { - destIndex: string; + color?: string; + destIndex?: string; } -export const IndexPatternPrompt: FC<Props> = ({ destIndex }) => { +export const IndexPatternPrompt: FC<Props> = ({ destIndex, color }) => { const { services: { http: { basePath }, @@ -30,20 +31,20 @@ export const IndexPatternPrompt: FC<Props> = ({ destIndex }) => { return ( <> - <EuiText size="xs" color="warning"> + <EuiText size="xs" color={color ?? 'warning'}> <FormattedMessage id="xpack.ml.dataframe.analytics.dataViewPromptMessage" defaultMessage="No data view exists for index {destIndex}. " values={{ - destIndex, + destIndex: destIndex ?? '', }} /> {canCreateDataView === true ? ( <FormattedMessage id="xpack.ml.dataframe.analytics.dataViewPromptLink" - defaultMessage="{linkToDataViewManagement} for {destIndex}." + defaultMessage="{linkToDataViewManagement}{destIndex}." values={{ - destIndex, + destIndex: destIndex ? ` for ${destIndex}` : '', linkToDataViewManagement: ( <EuiLink href={`${basePath.get()}/app/management/kibana/dataViews/create`} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx index 8086ee4f573bc..93ceccf2756dc 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx @@ -19,7 +19,12 @@ import { import { useScatterplotFieldOptions } from '../../../../../components/scatterplot_matrix'; import { SavedSearchQuery } from '../../../../../contexts/ml'; -import { defaultSearchQuery, isOutlierAnalysis, useResultsViewConfig } from '../../../../common'; +import { + defaultSearchQuery, + isOutlierAnalysis, + useResultsViewConfig, + getDestinationIndex, +} from '../../../../common'; import { FEATURE_INFLUENCE } from '../../../../common/constants'; import { @@ -33,6 +38,7 @@ import { getFeatureCount } from './common'; import { useOutlierData } from './use_outlier_data'; import { useExplorationUrlState } from '../../hooks/use_exploration_url_state'; import { ExplorationQueryBarProps } from '../exploration_query_bar/exploration_query_bar'; +import { IndexPatternPrompt } from '../index_pattern_prompt'; export type TableItem = Record<string, any>; @@ -90,6 +96,7 @@ export const OutlierExploration: FC<ExplorationProps> = React.memo(({ jobId }) = jobConfig?.analyzed_fields?.excludes, resultsField ); + const destIndex = getDestinationIndex(jobConfig); if (indexPatternErrorMessage !== undefined) { return ( @@ -101,7 +108,12 @@ export const OutlierExploration: FC<ExplorationProps> = React.memo(({ jobId }) = color="danger" iconType="cross" > - <p>{indexPatternErrorMessage}</p> + <p> + {indexPatternErrorMessage} + {needsDestIndexPattern ? ( + <IndexPatternPrompt destIndex={destIndex} color="text" /> + ) : null} + </p> </EuiCallOut> </EuiPanel> ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx index 7d2798f020242..efe5275685e9c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_action_name.tsx @@ -5,10 +5,12 @@ * 2.0. */ -import { EuiToolTip } from '@elastic/eui'; +import { EuiToolTip, EuiLink, EuiText } from '@elastic/eui'; import React, { FC } from 'react'; import { cloneDeep, isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import { DeepReadonly } from '../../../../../../../common/types/common'; import { DataFrameAnalyticsConfig, isOutlierAnalysis } from '../../../../common'; import { isClassificationAnalysis, isRegressionAnalysis } from '../../../../common/analytics'; @@ -401,9 +403,15 @@ export const useNavigateToWizardWithClonedJob = () => { services: { notifications: { toasts }, data: { dataViews }, + http: { basePath }, + application: { capabilities }, + theme, }, } = useMlKibana(); + const theme$ = theme.theme$; const navigateToPath = useNavigateToPath(); + const canCreateDataView = + capabilities.savedObjectsManagement.edit === true || capabilities.indexPatterns.save === true; return async (item: Pick<DataFrameAnalyticsListRow, 'config' | 'stats'>) => { const sourceIndex = Array.isArray(item.config.source.index) @@ -416,13 +424,42 @@ export const useNavigateToWizardWithClonedJob = () => { if (dv !== undefined) { sourceIndexId = dv.id; } else { - toasts.addDanger( - i18n.translate('xpack.ml.dataframe.analyticsList.noSourceDataViewForClone', { - defaultMessage: - 'Unable to clone the analytics job. No data view exists for index {dataView}.', - values: { dataView: sourceIndex }, - }) - ); + toasts.addDanger({ + title: toMountPoint( + wrapWithTheme( + <> + <FormattedMessage + id="xpack.ml.dataframe.analyticsList.noSourceDataViewForClone" + defaultMessage="Unable to clone the analytics job. No data view exists for index {sourceIndex}." + values={{ sourceIndex }} + /> + {canCreateDataView ? ( + <EuiText size="xs" color="text"> + <FormattedMessage + id="xpack.ml.dataframe.analytics.cloneAction.dataViewPromptLink" + defaultMessage="{linkToDataViewManagement}" + values={{ + linkToDataViewManagement: ( + <EuiLink + href={`${basePath.get()}/app/management/kibana/dataViews/create`} + target="_blank" + > + <FormattedMessage + id="xpack.ml.dataframe.analytics.cloneAction.dataViewPromptLinkText" + defaultMessage="Create a data view for {sourceIndex}" + values={{ sourceIndex }} + /> + </EuiLink> + ), + }} + /> + </EuiText> + ) : null} + </>, + theme$ + ) + ), + }); } } catch (e) { const error = extractErrorMessage(e); diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.tsx b/x-pack/plugins/ml/public/application/explorer/explorer.tsx index 5fcab05f068b8..35fa1baf5460b 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.tsx +++ b/x-pack/plugins/ml/public/application/explorer/explorer.tsx @@ -609,6 +609,7 @@ export const Explorer: FC<ExplorerUIProps> = ({ tableData={tableData} influencerFilter={applyFilter} sourceIndicesWithGeoFields={sourceIndicesWithGeoFields} + selectedJobs={selectedJobs} /> </EuiPanel> )} diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_utils.ts b/x-pack/plugins/ml/public/application/explorer/explorer_utils.ts index 652517acc0dab..9fd9e4aaff576 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_utils.ts +++ b/x-pack/plugins/ml/public/application/explorer/explorer_utils.ts @@ -57,6 +57,7 @@ export interface ExplorerJob { bucketSpanSeconds: number; isSingleMetricViewerJob?: boolean; sourceIndices?: string[]; + modelPlotEnabled: boolean; } export function isExplorerJob(arg: unknown): arg is ExplorerJob { @@ -143,6 +144,7 @@ export function createJobs(jobs: CombinedJob[]): ExplorerJob[] { bucketSpanSeconds: bucketSpan!.asSeconds(), isSingleMetricViewerJob: isTimeSeriesViewJob(job), sourceIndices: job.datafeed_config.indices, + modelPlotEnabled: job.model_plot_config?.enabled === true, }; }); } diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts index 2ff7d4b024800..d174c4c2c7e3f 100644 --- a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts @@ -38,7 +38,7 @@ const redirectToJobsManagementPage = jest.fn(() => Promise.resolve()); describe('useResolver', () => { afterEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { jest.advanceTimersByTime(0); diff --git a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts index a4874b9c13dd1..ad78677ad0f1f 100644 --- a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts +++ b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.test.ts @@ -37,7 +37,7 @@ describe('AnomalyExplorerChartsService', () => { }; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); mlApiServicesMock.jobs.jobForCloning.mockImplementation(() => Promise.resolve({})); diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts index fbfdefed7950e..bbc2f36605483 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/results.ts @@ -26,6 +26,13 @@ import type { EntityField } from '../../../../common/util/anomaly_utils'; import type { InfluencersFilterQuery } from '../../../../common/types/es_client'; import type { ExplorerChartsData } from '../../../../common/types/results'; +export interface CategoryDefinition { + categoryId: number; + terms: string; + regex: string; + examples: string[]; +} + export const resultsApiProvider = (httpService: HttpService) => ({ getAnomaliesTableData( jobIds: string[], @@ -78,7 +85,7 @@ export const resultsApiProvider = (httpService: HttpService) => ({ getCategoryDefinition(jobId: string, categoryId: string) { const body = JSON.stringify({ jobId, categoryId }); - return httpService.http<any>({ + return httpService.http<CategoryDefinition>({ path: `${basePath()}/results/category_definition`, method: 'POST', body, diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 2005e7c878f7a..a4a12d3b17a6b 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -1317,6 +1317,12 @@ export class TimeSeriesExplorer extends React.Component { tableData={tableData} filter={this.tableFilter} sourceIndicesWithGeoFields={sourceIndicesWithGeoFields} + selectedJobs={[ + { + id: selectedJob.job_id, + modelPlotEnabled, + }, + ]} /> )} </TimeSeriesExplorerPage> diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.test.ts b/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.test.ts index 4231f08b00c45..112863f560df3 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.test.ts +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/use_anomaly_charts_input_resolver.test.ts @@ -48,7 +48,7 @@ describe('useAnomalyChartsInputResolver', () => { }; beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const jobIds = ['test-job']; embeddableInput = new BehaviorSubject({ diff --git a/x-pack/plugins/ml/public/embeddables/common/get_jobs_observable.ts b/x-pack/plugins/ml/public/embeddables/common/get_jobs_observable.ts index 451eb95b4f801..c2a329a0ba650 100644 --- a/x-pack/plugins/ml/public/embeddables/common/get_jobs_observable.ts +++ b/x-pack/plugins/ml/public/embeddables/common/get_jobs_observable.ts @@ -29,6 +29,7 @@ export function getJobsObservable( id: job.job_id, selected: true, bucketSpanSeconds: bucketSpan!.asSeconds(), + modelPlotEnabled: job.model_plot_config?.enabled === true, }; }); return explorerJobs; diff --git a/x-pack/plugins/ml/server/models/results_service/results_service.ts b/x-pack/plugins/ml/server/models/results_service/results_service.ts index 2b6ae2d2b51e7..deb222ef6b3bd 100644 --- a/x-pack/plugins/ml/server/models/results_service/results_service.ts +++ b/x-pack/plugins/ml/server/models/results_service/results_service.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { sortBy, slice, get, cloneDeep } from 'lodash'; import moment from 'moment'; import Boom from '@hapi/boom'; @@ -225,8 +226,8 @@ export function resultsServiceProvider(mlClient: MlClient, client?: IScopedClust anomalies: [], interval: 'second', }; - // @ts-expect-error incorrect search response type - if (body.hits.total.value > 0) { + + if ((body.hits.total as estypes.SearchTotalHits).value > 0) { let records: AnomalyRecordDoc[] = []; body.hits.hits.forEach((hit: any) => { records.push(hit._source); diff --git a/x-pack/plugins/monitoring/public/components/renderers/setup_mode.test.js b/x-pack/plugins/monitoring/public/components/renderers/setup_mode.test.js index f2e952c96e88f..7984b8c23cdc5 100644 --- a/x-pack/plugins/monitoring/public/components/renderers/setup_mode.test.js +++ b/x-pack/plugins/monitoring/public/components/renderers/setup_mode.test.js @@ -171,7 +171,7 @@ describe('SetupModeRenderer', () => { it('should use a new product found in the api response', () => { const newProduct = { id: 1 }; - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); jest.doMock('../../lib/setup_mode', () => ({ getSetupModeState: () => ({ supported: true, diff --git a/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx b/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx index 138018e104c8a..929425d2d150a 100644 --- a/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx @@ -133,7 +133,7 @@ describe('DatePicker', () => { }); it('enables auto-refresh when refreshPaused is false', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const { wrapper } = mountDatePicker({ rangeFrom: 'now-15m', rangeTo: 'now', @@ -148,7 +148,7 @@ describe('DatePicker', () => { }); it('disables auto-refresh when refreshPaused is true', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); mountDatePicker({ rangeFrom: 'now-15m', rangeTo: 'now', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts index d1fdd6bc7cf67..ecc58776c07f6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts @@ -41,7 +41,7 @@ describe('Lens Attribute', () => { seriesConfig: reportViewConfig, seriesType: 'line', operationType: 'count', - indexPattern: mockDataView, + dataView: mockDataView, reportDefinitions: {}, time: { from: 'now-15m', to: 'now' }, color: 'green', @@ -76,7 +76,7 @@ describe('Lens Attribute', () => { seriesConfig: seriesConfigKpi, seriesType: 'line', operationType: 'count', - indexPattern: mockDataView, + dataView: mockDataView, reportDefinitions: { 'service.name': ['elastic-co'] }, time: { from: 'now-15m', to: 'now' }, color: 'green', @@ -107,7 +107,7 @@ describe('Lens Attribute', () => { from: 'now-1h', to: 'now', }, - indexPattern: mockDataView, + dataView: mockDataView, name: 'ux-series-1', breakdown: 'percentile', reportDefinitions: {}, @@ -218,7 +218,7 @@ describe('Lens Attribute', () => { seriesConfig: reportViewConfig, seriesType: 'line', operationType: 'count', - indexPattern: mockDataView, + dataView: mockDataView, reportDefinitions: { 'performance.metric': [LCP_FIELD] }, time: { from: 'now-15m', to: 'now' }, color: 'green', @@ -455,7 +455,7 @@ describe('Lens Attribute', () => { seriesConfig: reportViewConfig, seriesType: 'line', operationType: 'count', - indexPattern: mockDataView, + dataView: mockDataView, reportDefinitions: { 'performance.metric': [LCP_FIELD] }, breakdown: USER_AGENT_NAME, time: { from: 'now-15m', to: 'now' }, @@ -646,7 +646,7 @@ describe('Lens Attribute', () => { seriesConfig: reportViewConfig, seriesType: 'line', operationType: 'count', - indexPattern: mockDataView, + dataView: mockDataView, reportDefinitions: { 'performance.metric': [LCP_FIELD] }, time: { from: 'now-15m', to: 'now' }, color: 'green', @@ -667,7 +667,7 @@ describe('Lens Attribute', () => { const layerConfig1: LayerConfig = { seriesConfig: reportViewConfig, seriesType: 'line', - indexPattern: mockDataView, + dataView: mockDataView, reportDefinitions: {}, time: { from: 'now-15m', to: 'now' }, color: 'green', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 60f554d5344c4..9d39286f68998 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -37,6 +37,7 @@ import { } from '@kbn/lens-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/common'; import { PersistableFilter } from '@kbn/lens-plugin/common'; +import { DataViewSpec } from '@kbn/data-views-plugin/common'; import { LegendSize } from '@kbn/visualizations-plugin/common/constants'; import { urlFiltersToKueryString } from '../utils/stringify_kueries'; import { @@ -141,7 +142,7 @@ export interface LayerConfig { operationType?: OperationType; reportDefinitions: URLReportDefinition; time: { to: string; from: string }; - indexPattern: DataView; // TODO: Figure out if this can be renamed or if it's a Lens requirement + dataView: DataView; selectedMetricField: string; color: string; name: string; @@ -158,7 +159,7 @@ export class LensAttributes { { layerData: PersistedIndexPatternLayer; layerState: XYState['layers']; - indexPattern: DataView; + dataView: DataView; } >; globalFilter?: { query: string; language: string }; @@ -503,7 +504,7 @@ export class LensAttributes { return this.getBreakdownColumn({ layerId, layerConfig, - indexPattern: layerConfig.indexPattern, + indexPattern: layerConfig.dataView, sourceField: layerConfig.breakdown || layerConfig.seriesConfig.breakdownFields[0], labels: layerConfig.seriesConfig.labels, }); @@ -562,7 +563,7 @@ export class LensAttributes { layerId, formula, label: columnLabel ?? label, - dataView: layerConfig.indexPattern, + dataView: layerConfig.dataView, lensFormulaHelper: this.lensFormulaHelper!, }).main; } @@ -664,7 +665,7 @@ export class LensAttributes { showPercentileAnnotations, formula, } = metricOption; - const fieldMeta = layerConfig.indexPattern.getFieldByName(fieldName!); + const fieldMeta = layerConfig.dataView.getFieldByName(fieldName!); return { formula, palette, @@ -679,7 +680,7 @@ export class LensAttributes { layerConfig.showPercentileAnnotations ?? showPercentileAnnotations, }; } else { - const fieldMeta = layerConfig.indexPattern.getFieldByName(sourceField); + const fieldMeta = layerConfig.dataView.getFieldByName(sourceField); return { fieldMeta, fieldName: sourceField }; } @@ -706,7 +707,7 @@ export class LensAttributes { label, layerId, columnFilter, - dataView: layerConfig.indexPattern, + dataView: layerConfig.dataView, lensFormulaHelper: this.lensFormulaHelper!, }).main, ]; @@ -767,7 +768,7 @@ export class LensAttributes { label: mainLabel, layerId, columnFilter, - dataView: layerConfig.indexPattern, + dataView: layerConfig.dataView, lensFormulaHelper: this.lensFormulaHelper!, }).supportingColumns; } @@ -780,7 +781,7 @@ export class LensAttributes { label: columnLabel, layerId, formula, - dataView: layerConfig.indexPattern, + dataView: layerConfig.dataView, lensFormulaHelper: this.lensFormulaHelper!, }).supportingColumns; } @@ -1005,7 +1006,7 @@ export class LensAttributes { ? this.getBreakdownColumn({ layerId, sourceField: breakdown!, - indexPattern: layerConfig.indexPattern, + indexPattern: layerConfig.dataView, labels: layerConfig.seriesConfig.labels, layerConfig, }) @@ -1098,10 +1099,9 @@ export class LensAttributes { /* if the fields format matches the field format of the first layer, use the default y axis (right) * if not, use the secondary y axis (left) */ axisMode: - layerConfig.indexPattern.fieldFormatMap[layerConfig.selectedMetricField]?.id === - this.layerConfigs[0].indexPattern.fieldFormatMap[ - this.layerConfigs[0].selectedMetricField - ]?.id + layerConfig.dataView.fieldFormatMap[layerConfig.selectedMetricField]?.id === + this.layerConfigs[0].dataView.fieldFormatMap[this.layerConfigs[0].selectedMetricField] + ?.id ? ('left' as YAxisMode) : ('right' as YAxisMode), }, @@ -1127,11 +1127,7 @@ export class LensAttributes { return [...dataLayers, ...referenceLineLayers]; } - addThresholdLayer( - fieldName: string, - layerId: string, - { seriesConfig, indexPattern }: LayerConfig - ) { + addThresholdLayer(fieldName: string, layerId: string, { seriesConfig, dataView }: LayerConfig) { const referenceLineLayerId = `${layerId}-reference-lines`; const referenceLineColumns = this.getThresholdColumns( @@ -1148,7 +1144,7 @@ export class LensAttributes { const layerState = this.getThresholdLayer(fieldName, referenceLineLayerId, seriesConfig); - this.seriesReferenceLines[referenceLineLayerId] = { layerData, layerState, indexPattern }; + this.seriesReferenceLines[referenceLineLayerId] = { layerData, layerState, dataView }; } getThresholdLayer( @@ -1191,41 +1187,62 @@ export class LensAttributes { getReferences() { const uniqueIndexPatternsIds = Array.from( - new Set([...this.layerConfigs.map(({ indexPattern }) => indexPattern.id)]) + new Set([...this.layerConfigs.map(({ dataView }) => dataView.id!)]) ); + const adHocDataViews: Record<string, DataViewSpec> = {}; + const referenceLineIndexReferences = Object.entries(this.seriesReferenceLines).map( - ([id, { indexPattern }]) => ({ - id: indexPattern.id!, - name: getLayerReferenceName(id), - type: 'index-pattern', - }) + ([id, { dataView }]) => { + adHocDataViews[dataView.id!] = dataView.toSpec(false); + return { + id: dataView.id!, + name: getLayerReferenceName(id), + type: 'index-pattern', + }; + } ); - return [ - ...uniqueIndexPatternsIds.map((patternId) => ({ - id: patternId!, + const internalReferences = [ + ...uniqueIndexPatternsIds.map((dataViewId) => ({ + id: dataViewId, name: 'indexpattern-datasource-current-indexpattern', type: 'index-pattern', })), - ...this.layerConfigs.map(({ indexPattern }, index) => ({ - id: indexPattern.id!, - name: getLayerReferenceName(`layer${index}`), - type: 'index-pattern', - })), + ...this.layerConfigs.map(({ dataView }, index) => { + adHocDataViews[dataView.id!] = dataView.toSpec(false); + + return { + id: dataView.id!, + name: getLayerReferenceName(`layer${index}`), + type: 'index-pattern', + }; + }), ...referenceLineIndexReferences, ]; + + Object.entries(this.seriesReferenceLines).map(([id, { dataView }]) => ({ + id: dataView.id!, + name: getLayerReferenceName(id), + type: 'index-pattern', + })); + + return { internalReferences, adHocDataViews }; } getJSON(lastRefresh?: number): TypedLensByValueInput['attributes'] { const query = this.globalFilter || this.layerConfigs[0].seriesConfig.query; + const { internalReferences, adHocDataViews } = this.getReferences(); + return { title: 'Prefilled from exploratory view app', description: lastRefresh ? `Last refreshed at ${new Date(lastRefresh).toISOString()}` : '', visualizationType: 'lnsXY', - references: this.getReferences(), + references: [], state: { + internalReferences, + adHocDataViews, datasourceStates: { formBased: { layers: this.layers, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts index d6c5bfea5f1d6..cec2617feacf0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.test.ts @@ -16,7 +16,7 @@ import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames'; import { lensPluginMock } from '@kbn/lens-plugin/public/mocks'; import { FormulaPublicApi } from '@kbn/lens-plugin/public'; import { DataTypes } from '../constants'; -import { sampleMetricFormulaAttribute } from './sample_formula_metric_attribute'; +import { sampleMetricFormulaAttribute } from '../test_data/test_formula_metric_attribute'; describe('SingleMetricAttributes', () => { mockAppDataView(); @@ -35,7 +35,7 @@ describe('SingleMetricAttributes', () => { const layerConfig: LayerConfig = { seriesConfig: reportViewConfig, operationType: 'median', - indexPattern: mockDataView, + dataView: mockDataView, reportDefinitions: {}, time: { from: 'now-15m', to: 'now' }, color: 'green', @@ -60,19 +60,21 @@ describe('SingleMetricAttributes', () => { const jsonAttr = lnsAttr.getJSON(); expect(jsonAttr).toEqual({ description: 'undefined', - references: [ - { - id: 'apm-*', - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: 'apm-*', - name: 'indexpattern-datasource-layer-layer0', - type: 'index-pattern', - }, - ], + references: [], state: { + adHocDataViews: { [mockDataView.title]: mockDataView.toSpec(false) }, + internalReferences: [ + { + id: 'apm-*', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0', + type: 'index-pattern', + }, + ], datasourceStates: { formBased: { layers: { @@ -122,19 +124,21 @@ describe('SingleMetricAttributes', () => { const jsonAttr = lnsAttr.getJSON(); expect(jsonAttr).toEqual({ description: 'undefined', - references: [ - { - id: 'apm-*', - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: 'apm-*', - name: 'indexpattern-datasource-layer-layer0', - type: 'index-pattern', - }, - ], + references: [], state: { + adHocDataViews: { [mockDataView.title]: mockDataView.toSpec(false) }, + internalReferences: [ + { + id: 'apm-*', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0', + type: 'index-pattern', + }, + ], datasourceStates: { formBased: { layers: { @@ -188,7 +192,7 @@ describe('SingleMetricAttributes', () => { const layerConfigFormula: LayerConfig = { seriesConfig: reportViewConfigFormula, operationType: 'median', - indexPattern: mockDataView, + dataView: mockDataView, reportDefinitions: {}, time: { from: 'now-15m', to: 'now' }, color: 'green', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts index 9a1f5498dc17e..1a7fe32d6c40a 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/single_metric_attributes.ts @@ -15,7 +15,7 @@ import { import type { DataView } from '@kbn/data-views-plugin/common'; import { Query } from '@kbn/es-query'; -import { FORMULA_COLUMN } from '../constants'; +import { FORMULA_COLUMN, RECORDS_FIELD } from '../constants'; import { ColumnFilter, MetricOption } from '../../types'; import { SeriesConfig } from '../../../../..'; import { @@ -48,7 +48,7 @@ export class SingleMetricLensAttributes extends LensAttributes { } getSingleMetricLayer() { - const { seriesConfig, selectedMetricField, operationType, indexPattern } = this.layerConfigs[0]; + const { seriesConfig, selectedMetricField, operationType, dataView } = this.layerConfigs[0]; const metricOption = parseCustomFieldName(seriesConfig, selectedMetricField); @@ -69,7 +69,7 @@ export class SingleMetricLensAttributes extends LensAttributes { return this.getFormulaLayer({ formula, label: columnLabel, - dataView: indexPattern, + dataView, format, filter: columnFilter, }); @@ -105,7 +105,7 @@ export class SingleMetricLensAttributes extends LensAttributes { [this.columnId]: { ...buildNumberColumn(sourceField), label: columnLabel ?? '', - operationType: sourceField === 'Records' ? 'count' : operationType || 'median', + operationType: sourceField === RECORDS_FIELD ? 'count' : operationType || 'median', filter: columnFilter, }, }, @@ -197,12 +197,16 @@ export class SingleMetricLensAttributes extends LensAttributes { const visualization = this.getMetricState(); + const { internalReferences, adHocDataViews } = this.getReferences(); + return { title: 'Prefilled from exploratory view app', description: String(refresh), visualizationType: 'lnsLegacyMetric', - references: this.getReferences(), + references: [], state: { + internalReferences, + adHocDataViews, visualization, datasourceStates: { formBased: { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.test.ts index e8b5c4b9abd4b..f7d415d05d551 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.test.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/core_web_vitals_config.test.ts @@ -30,7 +30,7 @@ describe('Core web vital config test', function () { color: 'green', name: 'test-series', breakdown: USER_AGENT_OS, - indexPattern: mockDataView, + dataView: mockDataView, time: { from: 'now-15m', to: 'now' }, reportDefinitions: { [SERVICE_NAME]: ['elastic-co'] }, selectedMetricField: LCP_FIELD, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts index c67857ae7c0a4..56129ffd86da8 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/single_metric_config.ts @@ -6,12 +6,14 @@ */ import { i18n } from '@kbn/i18n'; +import { LegacyMetricState } from '@kbn/lens-plugin/common'; +import { euiPaletteForStatus } from '@elastic/eui'; import { SYNTHETICS_STEP_DURATION, SYNTHETICS_STEP_NAME, } from '../constants/field_names/synthetics'; import { ConfigProps, SeriesConfig } from '../../types'; -import { FieldLabels, FORMULA_COLUMN } from '../constants'; +import { FieldLabels, FORMULA_COLUMN, RECORDS_FIELD } from '../constants'; import { buildExistsFilter } from '../utils'; export function getSyntheticsSingleMetricConfig({ dataView }: ConfigProps): SeriesConfig { @@ -93,35 +95,54 @@ export function getSyntheticsSingleMetricConfig({ dataView }: ConfigProps): Seri }, { id: 'monitor_errors', - field: 'state.id', label: i18n.translate('xpack.observability.expView.errors', { defaultMessage: 'Errors', }), metricStateOptions: { titlePosition: 'bottom', colorMode: 'Labels', - palette: { - name: 'custom', - type: 'palette', - params: { - steps: 3, - name: 'custom', - reverse: false, - rangeType: 'number', - rangeMin: 0, - progression: 'fixed', - stops: [{ color: '#E7664C', stop: 100 }], - colorStops: [{ color: '#E7664C', stop: 0 }], - continuity: 'above', - maxSteps: 5, - }, - }, + palette: getColorPalette('danger'), }, columnType: FORMULA_COLUMN, formula: 'unique_count(state.id, kql=\'monitor.status: "down"\')', format: 'number', }, + { + id: 'monitor_failed_tests', + label: i18n.translate('xpack.observability.expView.failedTests', { + defaultMessage: 'Failed tests', + }), + metricStateOptions: { + titlePosition: 'bottom', + }, + field: RECORDS_FIELD, + format: 'number', + columnFilter: { language: 'kuery', query: 'summary.down > 0' }, + }, ], labels: FieldLabels, }; } + +const getColorPalette = (color: 'danger' | 'warning' | 'success'): LegacyMetricState['palette'] => { + const statusPalette = euiPaletteForStatus(5); + + // TODO: add more colors + + return { + name: 'custom', + type: 'palette', + params: { + steps: 3, + name: 'custom', + reverse: false, + rangeType: 'number', + rangeMin: 0, + progression: 'fixed', + stops: [{ color: statusPalette[3], stop: 100 }], + colorStops: [{ color: statusPalette[3], stop: 0 }], + continuity: 'above', + maxSteps: 5, + }, + }; +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts index 1af87c385d31e..d4985c4ed5173 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/mobile_test_attribute.ts @@ -5,23 +5,27 @@ * 2.0. */ +import { mockDataView } from '../../rtl_helpers'; + export const testMobileKPIAttr = { title: 'Prefilled from exploratory view app', description: '', - references: [ - { - id: 'apm-*', - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: 'apm-*', - name: 'indexpattern-datasource-layer-layer0', - type: 'index-pattern', - }, - ], + references: [], visualizationType: 'lnsXY', state: { + adHocDataViews: { [mockDataView.title]: mockDataView.toSpec(false) }, + internalReferences: [ + { + id: 'apm-*', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0', + type: 'index-pattern', + }, + ], datasourceStates: { formBased: { layers: { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts index 4661775b3a83f..d9188537947c0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute.ts @@ -4,28 +4,31 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { mockDataView } from '../../rtl_helpers'; import { RECORDS_FIELD } from '../constants'; export const sampleAttribute = { description: '', - references: [ - { - id: 'apm-*', - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: 'apm-*', - name: 'indexpattern-datasource-layer-layer0', - type: 'index-pattern', - }, - { - id: 'apm-*', - name: 'indexpattern-datasource-layer-layer0-reference-lines', - type: 'index-pattern', - }, - ], + references: [], state: { + internalReferences: [ + { + id: 'apm-*', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0-reference-lines', + type: 'index-pattern', + }, + ], + adHocDataViews: { [mockDataView.title]: mockDataView.toSpec(false) }, datasourceStates: { formBased: { layers: { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts index 15e462c10be28..d87517e761342 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_cwv.ts @@ -4,23 +4,26 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { mockDataView } from '../../rtl_helpers'; import { RECORDS_FIELD } from '../constants'; export const sampleAttributeCoreWebVital = { description: '', - references: [ - { - id: 'apm-*', - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: 'apm-*', - name: 'indexpattern-datasource-layer-layer0', - type: 'index-pattern', - }, - ], + references: [], state: { + internalReferences: [ + { + id: 'apm-*', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0', + type: 'index-pattern', + }, + ], + adHocDataViews: { [mockDataView.title]: mockDataView.toSpec(false) }, datasourceStates: { formBased: { layers: { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts index 6482795198898..b22bedc073931 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_kpi.ts @@ -4,23 +4,26 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { mockDataView } from '../../rtl_helpers'; import { RECORDS_FIELD } from '../constants'; export const sampleAttributeKpi = { description: '', - references: [ - { - id: 'apm-*', - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: 'apm-*', - name: 'indexpattern-datasource-layer-layer0', - type: 'index-pattern', - }, - ], + references: [], state: { + internalReferences: [ + { + id: 'apm-*', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0', + type: 'index-pattern', + }, + ], + adHocDataViews: { [mockDataView.title]: mockDataView.toSpec(false) }, datasourceStates: { formBased: { layers: { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts index 873e4a6269de6..dfa15ae71b1f0 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/sample_attribute_with_reference_lines.ts @@ -4,28 +4,31 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { mockDataView } from '../../rtl_helpers'; import { RECORDS_FIELD } from '../constants'; export const sampleAttributeWithReferenceLines = { description: '', - references: [ - { - id: 'apm-*', - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: 'apm-*', - name: 'indexpattern-datasource-layer-layer0', - type: 'index-pattern', - }, - { - id: 'apm-*', - name: 'indexpattern-datasource-layer-layer0-reference-lines', - type: 'index-pattern', - }, - ], + references: [], state: { + internalReferences: [ + { + id: 'apm-*', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0-reference-lines', + type: 'index-pattern', + }, + ], + adHocDataViews: { [mockDataView.title]: mockDataView.toSpec(false) }, datasourceStates: { formBased: { layers: { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/sample_formula_metric_attribute.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts similarity index 92% rename from x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/sample_formula_metric_attribute.ts rename to x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts index 6894838d705e8..1a3ef129fa94f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes/sample_formula_metric_attribute.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/test_data/test_formula_metric_attribute.ts @@ -5,21 +5,25 @@ * 2.0. */ +import { mockDataView } from '../../rtl_helpers'; + export const sampleMetricFormulaAttribute = { description: 'undefined', - references: [ - { - id: 'apm-*', - name: 'indexpattern-datasource-current-indexpattern', - type: 'index-pattern', - }, - { - id: 'apm-*', - name: 'indexpattern-datasource-layer-layer0', - type: 'index-pattern', - }, - ], + references: [], state: { + adHocDataViews: { [mockDataView.title]: mockDataView.toSpec(false) }, + internalReferences: [ + { + id: 'apm-*', + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'apm-*', + name: 'indexpattern-datasource-layer-layer0', + type: 'index-pattern', + }, + ], datasourceStates: { formBased: { layers: { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/contexts/exploratory_view_config.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/contexts/exploratory_view_config.tsx index 8986705426936..c024971198510 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/contexts/exploratory_view_config.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/contexts/exploratory_view_config.tsx @@ -18,7 +18,6 @@ interface ExploratoryViewContextValue { reportType: ReportViewType | typeof SELECT_REPORT_TYPE; label: string; }>; - dataViews: Record<string, string>; reportConfigMap: ReportConfigMap; asPanel?: boolean; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; @@ -27,15 +26,14 @@ interface ExploratoryViewContextValue { setIsEditMode?: React.Dispatch<React.SetStateAction<boolean>>; } -export const ExploratoryViewContext = createContext<ExploratoryViewContextValue>({ - dataViews: {}, -} as ExploratoryViewContextValue); +export const ExploratoryViewContext = createContext<ExploratoryViewContextValue>( + {} as ExploratoryViewContextValue +); export function ExploratoryViewContextProvider({ children, reportTypes, dataTypes, - dataViews, reportConfigMap, setHeaderActionMenu, asPanel = true, @@ -47,7 +45,6 @@ export function ExploratoryViewContextProvider({ asPanel, reportTypes, dataTypes, - dataViews, reportConfigMap, setHeaderActionMenu, theme$, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx index dad9d5cef3f70..f74ea50157241 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/embeddable/index.tsx @@ -54,7 +54,7 @@ export function getExploratoryViewEmbeddable( setLoading(true); try { - const obsvIndexP = new ObservabilityDataViews(dataViews); + const obsvIndexP = new ObservabilityDataViews(dataViews, true); const indPattern = await obsvIndexP.getDataView( dataType, dataTypesIndexPatterns?.[dataType] diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_data_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_data_view.tsx index 3904329f0a5c7..d5092aa67d846 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_data_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_data_view.tsx @@ -12,10 +12,10 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { DataViewInsufficientAccessError } from '@kbn/data-views-plugin/common'; import { AppDataType } from '../types'; import { ObservabilityPublicPluginsStart } from '../../../../plugin'; -import { ObservabilityDataViews } from '../../../../utils/observability_data_views'; -import { getDataHandler } from '../../../../data_handler'; -import { useExploratoryView } from '../contexts/exploratory_view_config'; -import { getApmDataViewTitle } from '../utils/utils'; +import { + getDataTypeIndices, + ObservabilityDataViews, +} from '../../../../utils/observability_data_views'; export interface DataViewContext { loading: boolean; @@ -46,48 +46,18 @@ export function DataViewContextProvider({ children }: ProviderProps) { services: { dataViews: dataViewsService }, } = useKibana<ObservabilityPublicPluginsStart>(); - const { dataViews: dataViewsList } = useExploratoryView(); - const loadDataView: DataViewContext['loadDataView'] = useCallback( async ({ dataType }) => { if (typeof hasAppData[dataType] === 'undefined' && !loading[dataType]) { setLoading((prevState) => ({ ...prevState, [dataType]: true })); try { - let hasDataT = false; - let indices: string | undefined = ''; - if (dataViewsList[dataType]) { - indices = dataViewsList[dataType]; - hasDataT = true; - } - switch (dataType) { - case 'ux': - case 'synthetics': - const resultUx = await getDataHandler(dataType)?.hasData(); - hasDataT = Boolean(resultUx?.hasData); - indices = resultUx?.indices; - break; - case 'infra_metrics': - const resultMetrics = await getDataHandler(dataType)?.hasData(); - hasDataT = Boolean(resultMetrics?.hasData); - indices = resultMetrics?.indices; - break; - case 'infra_logs': - const resultLogs = await getDataHandler(dataType)?.hasData(); - hasDataT = Boolean(resultLogs?.hasData); - indices = resultLogs?.indices; - break; - case 'apm': - case 'mobile': - const resultApm = await getDataHandler('apm')!.hasData(); - hasDataT = Boolean(resultApm?.hasData); - indices = getApmDataViewTitle(resultApm?.indices); - break; - } - setHasAppData((prevState) => ({ ...prevState, [dataType]: hasDataT })); + const { indices, hasData } = await getDataTypeIndices(dataType); + + setHasAppData((prevState) => ({ ...prevState, [dataType]: hasData })); - if (hasDataT && indices) { - const obsvDataV = new ObservabilityDataViews(dataViewsService); + if (hasData && indices) { + const obsvDataV = new ObservabilityDataViews(dataViewsService, true); const dataV = await obsvDataV.getDataView(dataType, indices); setDataViews((prevState) => ({ ...prevState, [dataType]: dataV })); @@ -104,7 +74,7 @@ export function DataViewContextProvider({ children }: ProviderProps) { } } }, - [dataViewsService, hasAppData, dataViewsList, loading] + [dataViewsService, hasAppData, loading] ); return ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.test.tsx index 623c5dd3e083b..1d5de56f9f2a7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.test.tsx @@ -71,7 +71,6 @@ describe('useExpViewTimeRange', function () { <ExploratoryViewContextProvider reportTypes={reportTypesList} dataTypes={dataTypes} - dataViews={{}} reportConfigMap={obsvReportConfigMap} setHeaderActionMenu={jest.fn()} theme$={themeServiceMock.createTheme$()} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index 1b0f4de4fa291..1f1997d394846 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -74,7 +74,7 @@ export function getLayerConfigs( layerConfigs.push({ filters, - indexPattern: dataView, + dataView, seriesConfig, time: series.time, name: series.name, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/obsv_exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/obsv_exploratory_view.tsx index 443d96f855b76..14d5101a561fb 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/obsv_exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/obsv_exploratory_view.tsx @@ -118,7 +118,6 @@ export function ObservabilityExploratoryView() { <ExploratoryViewContextProvider reportTypes={reportTypesList} dataTypes={dataTypes} - dataViews={{}} reportConfigMap={obsvReportConfigMap} setHeaderActionMenu={appMountParameters.setHeaderActionMenu} theme$={appMountParameters.theme$} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx index 8e0ac0d3505f5..e4969eb5e439e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/rtl_helpers.tsx @@ -207,7 +207,6 @@ export function render<ExtraCore>( <ExploratoryViewContextProvider reportTypes={reportTypesList} dataTypes={dataTypes} - dataViews={{}} reportConfigMap={obsvReportConfigMap} setHeaderActionMenu={jest.fn()} theme$={themeServiceMock.createTheme$()} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index 075feba5467ec..dc195fc08c016 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -23,6 +23,7 @@ import { FieldFormatParams as BaseFieldFormatParams, SerializedFieldFormat, } from '@kbn/field-formats-plugin/common'; +import { TermsIndexPatternColumn } from '@kbn/lens-plugin/public'; import { FORMULA_COLUMN } from './configurations/constants'; export const ReportViewTypes = { @@ -167,3 +168,7 @@ export interface BuilderItem { } export type SupportedOperations = 'average' | 'median' | 'sum' | 'unique_count' | 'min' | 'max'; + +type TermColumnParams = TermsIndexPatternColumn['params']; + +export type TermColumnParamsOrderBy = TermColumnParams['orderBy']; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/utils.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/utils.ts index 65e763e148700..0edf6ff0c19e4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/utils.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/utils.ts @@ -8,6 +8,9 @@ import { uniq } from 'lodash'; import { ApmIndicesConfig } from '../../../../../common/typings'; -export function getApmDataViewTitle(apmIndicesConfig: ApmIndicesConfig) { +export function getApmDataViewTitle(apmIndicesConfig?: ApmIndicesConfig) { + if (!apmIndicesConfig) { + return; + } return uniq([apmIndicesConfig.transaction, apmIndicesConfig.metric]).join(','); } diff --git a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.test.ts b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.test.ts index e1d930d539842..fc71b76b27e64 100644 --- a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.test.ts +++ b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.test.ts @@ -67,10 +67,11 @@ const fieldFormats = { }, }; -describe('ObservabilityIndexPatterns', function () { +describe('ObservabilityDataViews', function () { const { dataViews } = mockCore(); dataViews!.get = jest.fn().mockReturnValue({ title: 'index-*' }); dataViews!.createAndSave = jest.fn().mockReturnValue({ id: dataViewList.ux }); + dataViews!.create = jest.fn().mockReturnValue({ id: dataViewList.ux }); dataViews!.updateSavedObject = jest.fn(); it('should return index pattern for app', async function () { diff --git a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts index 8285e1d66e285..241e2ba021f3e 100644 --- a/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts +++ b/x-pack/plugins/observability/public/utils/observability_data_views/observability_data_views.ts @@ -14,6 +14,7 @@ import type { } from '@kbn/data-views-plugin/public'; import { RuntimeField } from '@kbn/data-views-plugin/public'; import { syntheticsRuntimeFields } from '../../components/shared/exploratory_view/configurations/synthetics/runtime_fields'; +import { getApmDataViewTitle } from '../../components/shared/exploratory_view/utils/utils'; import { rumFieldFormats } from '../../components/shared/exploratory_view/configurations/rum/field_formats'; import { syntheticsFieldFormats } from '../../components/shared/exploratory_view/configurations/synthetics/field_formats'; import { @@ -76,6 +77,22 @@ const getAppDataViewId = (app: AppDataType, indices: string) => { return `${dataViewList?.[app] ?? app}_${postfix}`; }; +export async function getDataTypeIndices(dataType: AppDataType) { + switch (dataType) { + case 'mobile': + case 'ux': + case 'apm': + const resultApm = await getDataHandler('apm')?.hasData(); + return { + hasData: Boolean(resultApm?.hasData), + indices: getApmDataViewTitle(resultApm?.indices), + }; + default: + const resultUx = await getDataHandler(dataType)?.hasData(); + return { hasData: Boolean(resultUx?.hasData), indices: resultUx?.indices as string }; + } +} + export function isParamsSame(param1: IFieldFormat['_params'], param2?: FieldFormatParams) { const isSame = param1?.inputFormat === param2?.inputFormat && @@ -91,18 +108,27 @@ export function isParamsSame(param1: IFieldFormat['_params'], param2?: FieldForm } export class ObservabilityDataViews { - dataViews?: DataViewsPublicPluginStart; + dataViews: DataViewsPublicPluginStart; + adHocDataViews: boolean = false; - constructor(dataViews: DataViewsPublicPluginStart) { + constructor(dataViews: DataViewsPublicPluginStart, adHocDataViews?: boolean) { this.dataViews = dataViews; + this.adHocDataViews = adHocDataViews ?? false; } async createDataView(app: AppDataType, indices: string) { - if (!this.dataViews) { - throw new Error('data is not defined'); - } + const appIndicesPattern = getAppIndicesWithPattern(app, indices); + return await this.dataViews.create({ + title: appIndicesPattern, + id: getAppDataViewId(app, indices), + timeFieldName: '@timestamp', + fieldFormats: this.getFieldFormats(app), + }); + } + async createAndSavedDataView(app: AppDataType, indices: string) { const appIndicesPattern = getAppIndicesWithPattern(app, indices); + return await this.dataViews.createAndSave({ title: appIndicesPattern, id: getAppDataViewId(app, indices), @@ -147,26 +173,10 @@ export class ObservabilityDataViews { return fieldFormatMap; } - async getDataTypeIndices(dataType: AppDataType) { - switch (dataType) { - case 'ux': - case 'synthetics': - const resultUx = await getDataHandler(dataType)?.hasData(); - return resultUx?.indices; - case 'apm': - case 'mobile': - const resultApm = await getDataHandler('apm')?.hasData(); - return resultApm?.indices.transaction; - } - } - async getDataView(app: AppDataType, indices?: string): Promise<DataView | undefined> { - if (!this.dataViews) { - throw new Error('data is not defined'); - } let appIndices = indices; if (!appIndices) { - appIndices = await this.getDataTypeIndices(app); + appIndices = (await getDataTypeIndices(app)).indices; } if (appIndices) { @@ -174,11 +184,16 @@ export class ObservabilityDataViews { const dataViewId = getAppDataViewId(app, appIndices); const dataViewTitle = getAppIndicesWithPattern(app, appIndices); // we will get the data view by id + + if (this.adHocDataViews) { + return await this.createDataView(app, appIndices); + } + const dataView = await this.dataViews?.get(dataViewId); // and make sure title matches, otherwise, we will need to create it if (dataView.title !== dataViewTitle) { - return await this.createDataView(app, appIndices); + return await this.createAndSavedDataView(app, appIndices); } // this is intentional a non blocking call, so no await clause @@ -186,7 +201,7 @@ export class ObservabilityDataViews { return dataView; } catch (e: unknown) { if (e instanceof SavedObjectNotFound) { - return await this.createDataView(app, appIndices); + return await this.createAndSavedDataView(app, appIndices); } } } diff --git a/x-pack/plugins/observability/server/assets/component_templates/slo_mappings_template.ts b/x-pack/plugins/observability/server/assets/component_templates/slo_mappings_template.ts index 550a50ba112a8..1611d3ee601bd 100644 --- a/x-pack/plugins/observability/server/assets/component_templates/slo_mappings_template.ts +++ b/x-pack/plugins/observability/server/assets/component_templates/slo_mappings_template.ts @@ -20,6 +20,9 @@ export const getSLOMappingsTemplate = (name: string) => ({ type: 'keyword', ignore_above: 256, }, + revision: { + type: 'long', + }, numerator: { type: 'long', }, @@ -29,6 +32,25 @@ export const getSLOMappingsTemplate = (name: string) => ({ context: { type: 'flattened', }, + _internal: { + properties: { + name: { type: 'keyword', ignore_above: 256 }, + budgeting_method: { type: 'keyword' }, + objective: { + properties: { + target: { type: 'double' }, + timeslice_target: { type: 'double' }, + timeslice_window: { type: 'keyword' }, + }, + }, + time_window: { + properties: { + duration: { type: 'keyword' }, + is_rolling: { type: 'boolean' }, + }, + }, + }, + }, }, }, }, diff --git a/x-pack/plugins/observability/server/errors/errors.ts b/x-pack/plugins/observability/server/errors/errors.ts index 8f04cc58bc4da..b32d0e2b5be51 100644 --- a/x-pack/plugins/observability/server/errors/errors.ts +++ b/x-pack/plugins/observability/server/errors/errors.ts @@ -18,3 +18,4 @@ export class SLONotFound extends ObservabilityError {} export class InternalQueryError extends ObservabilityError {} export class NotSupportedError extends ObservabilityError {} export class IllegalArgumentError extends ObservabilityError {} +export class InvalidTransformError extends ObservabilityError {} diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index ff5fd246bea1b..dd2a07f848db3 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -157,6 +157,9 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> { }); return { + getAlertDetailsConfig() { + return config.unsafe.alertDetails; + }, getScopedAnnotationsClient: async (...args: Parameters<ScopedAnnotationsClientFactory>) => { const api = await annotationsApiPromise; return api?.getScopedAnnotationsClient(...args); diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index 58b8a3f346044..21b3d46fe9b62 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -18,6 +18,7 @@ import { import { ApmTransactionDurationTransformGenerator, ApmTransactionErrorRateTransformGenerator, + KQLCustomTransformGenerator, TransformGenerator, } from '../../services/slo/transform_generators'; import { IndicatorTypes } from '../../types/models'; @@ -32,6 +33,7 @@ import { createObservabilityServerRoute } from '../create_observability_server_r const transformGenerators: Record<IndicatorTypes, TransformGenerator> = { 'slo.apm.transaction_duration': new ApmTransactionDurationTransformGenerator(), 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), + 'slo.kql.custom': new KQLCustomTransformGenerator(), }; const createSLORoute = createObservabilityServerRoute({ diff --git a/x-pack/plugins/observability/server/saved_objects/slo.ts b/x-pack/plugins/observability/server/saved_objects/slo.ts index a088765a988a3..461896a35f84e 100644 --- a/x-pack/plugins/observability/server/saved_objects/slo.ts +++ b/x-pack/plugins/observability/server/saved_objects/slo.ts @@ -42,8 +42,11 @@ export const slo: SavedObjectsType = { objective: { properties: { target: { type: 'float' }, + timeslice_target: { type: 'float' }, + timeslice_window: { type: 'keyword' }, }, }, + revision: { type: 'short' }, created_at: { type: 'date' }, updated_at: { type: 'date' }, }, diff --git a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts index a6d46b0689bb2..4c4139c0e9120 100644 --- a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts +++ b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts @@ -13,6 +13,7 @@ import { APMTransactionDurationIndicator, APMTransactionErrorRateIndicator, Indicator, + KQLCustomIndicator, SLO, } from '../../../types/models'; import { CreateSLOParams } from '../../../types/rest_specs'; @@ -45,6 +46,19 @@ export const createAPMTransactionDurationIndicator = ( }, }); +export const createKQLCustomIndicator = ( + params: Partial<KQLCustomIndicator['params']> = {} +): Indicator => ({ + type: 'slo.kql.custom', + params: { + index: 'my-index*', + query_filter: 'labels.groupId: group-3', + numerator: 'latency < 300', + denominator: '', + ...params, + }, +}); + const defaultSLO: Omit<SLO, 'id' | 'revision' | 'created_at' | 'updated_at'> = { name: 'irrelevant', description: 'irrelevant', diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap index 61fadab240ae7..187126b8e8efd 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap @@ -57,6 +57,31 @@ Object { "field": "@timestamp", }, }, + "slo._internal.budgeting_method": Object { + "terms": Object { + "field": "slo._internal.budgeting_method", + }, + }, + "slo._internal.name": Object { + "terms": Object { + "field": "slo._internal.name", + }, + }, + "slo._internal.objective.target": Object { + "terms": Object { + "field": "slo._internal.objective.target", + }, + }, + "slo._internal.time_window.duration": Object { + "terms": Object { + "field": "slo._internal.time_window.duration", + }, + }, + "slo._internal.time_window.is_rolling": Object { + "terms": Object { + "field": "slo._internal.time_window.is_rolling", + }, + }, "slo.id": Object { "terms": Object { "field": "slo.id", @@ -106,6 +131,36 @@ Object { }, }, "runtime_mappings": Object { + "slo._internal.budgeting_method": Object { + "script": Object { + "source": "emit('occurrences')", + }, + "type": "keyword", + }, + "slo._internal.name": Object { + "script": Object { + "source": "emit('irrelevant')", + }, + "type": "keyword", + }, + "slo._internal.objective.target": Object { + "script": Object { + "source": "emit(0.999)", + }, + "type": "double", + }, + "slo._internal.time_window.duration": Object { + "script": Object { + "source": "emit('7d')", + }, + "type": "keyword", + }, + "slo._internal.time_window.is_rolling": Object { + "script": Object { + "source": "emit(true)", + }, + "type": "boolean", + }, "slo.id": Object { "script": Object { "source": Any<String>, diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap index 51a0ee31581a2..3a7826cb9d8b5 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap @@ -62,6 +62,31 @@ Object { "field": "@timestamp", }, }, + "slo._internal.budgeting_method": Object { + "terms": Object { + "field": "slo._internal.budgeting_method", + }, + }, + "slo._internal.name": Object { + "terms": Object { + "field": "slo._internal.name", + }, + }, + "slo._internal.objective.target": Object { + "terms": Object { + "field": "slo._internal.objective.target", + }, + }, + "slo._internal.time_window.duration": Object { + "terms": Object { + "field": "slo._internal.time_window.duration", + }, + }, + "slo._internal.time_window.is_rolling": Object { + "terms": Object { + "field": "slo._internal.time_window.is_rolling", + }, + }, "slo.id": Object { "terms": Object { "field": "slo.id", @@ -111,6 +136,36 @@ Object { }, }, "runtime_mappings": Object { + "slo._internal.budgeting_method": Object { + "script": Object { + "source": "emit('occurrences')", + }, + "type": "keyword", + }, + "slo._internal.name": Object { + "script": Object { + "source": "emit('irrelevant')", + }, + "type": "keyword", + }, + "slo._internal.objective.target": Object { + "script": Object { + "source": "emit(0.999)", + }, + "type": "double", + }, + "slo._internal.time_window.duration": Object { + "script": Object { + "source": "emit('7d')", + }, + "type": "keyword", + }, + "slo._internal.time_window.is_rolling": Object { + "script": Object { + "source": "emit(true)", + }, + "type": "boolean", + }, "slo.id": Object { "script": Object { "source": Any<String>, diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap new file mode 100644 index 0000000000000..5fed8a8e08977 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/kql_custom.test.ts.snap @@ -0,0 +1,251 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KQL Custom Transform Generator aggregates using the denominator kql 1`] = ` +Object { + "filter": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "http.status_code", + }, + }, + ], + }, + }, +} +`; + +exports[`KQL Custom Transform Generator aggregates using the numerator kql 1`] = ` +Object { + "filter": Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "range": Object { + "latency": Object { + "lt": "400", + }, + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "http.status_code": "2xx", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "http.status_code": "3xx", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "http.status_code": "4xx", + }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, +} +`; + +exports[`KQL Custom Transform Generator filters the source using the kql query 1`] = ` +Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "labels.groupId": "group-4", + }, + }, + ], + }, +} +`; + +exports[`KQL Custom Transform Generator returns the correct transform params with every specified indicator params 1`] = ` +Object { + "_meta": Object { + "version": 1, + }, + "dest": Object { + "index": "slo-observability.sli-v1", + "pipeline": "slo-observability.sli.monthly", + }, + "frequency": "1m", + "pivot": Object { + "aggregations": Object { + "slo.denominator": Object { + "filter": Object { + "match_all": Object {}, + }, + }, + "slo.numerator": Object { + "filter": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "range": Object { + "latency": Object { + "lt": "300", + }, + }, + }, + ], + }, + }, + }, + }, + "group_by": Object { + "@timestamp": Object { + "date_histogram": Object { + "calendar_interval": "1m", + "field": "@timestamp", + }, + }, + "slo._internal.budgeting_method": Object { + "terms": Object { + "field": "slo._internal.budgeting_method", + }, + }, + "slo._internal.name": Object { + "terms": Object { + "field": "slo._internal.name", + }, + }, + "slo._internal.objective.target": Object { + "terms": Object { + "field": "slo._internal.objective.target", + }, + }, + "slo._internal.time_window.duration": Object { + "terms": Object { + "field": "slo._internal.time_window.duration", + }, + }, + "slo._internal.time_window.is_rolling": Object { + "terms": Object { + "field": "slo._internal.time_window.is_rolling", + }, + }, + "slo.id": Object { + "terms": Object { + "field": "slo.id", + }, + }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, + }, + }, + "settings": Object { + "deduce_mappings": false, + }, + "source": Object { + "index": "my-index*", + "query": Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "labels.groupId": "group-3", + }, + }, + ], + }, + }, + "runtime_mappings": Object { + "slo._internal.budgeting_method": Object { + "script": Object { + "source": "emit('occurrences')", + }, + "type": "keyword", + }, + "slo._internal.name": Object { + "script": Object { + "source": "emit('irrelevant')", + }, + "type": "keyword", + }, + "slo._internal.objective.target": Object { + "script": Object { + "source": "emit(0.999)", + }, + "type": "double", + }, + "slo._internal.time_window.duration": Object { + "script": Object { + "source": "emit('7d')", + }, + "type": "keyword", + }, + "slo._internal.time_window.is_rolling": Object { + "script": Object { + "source": "emit(true)", + }, + "type": "boolean", + }, + "slo.id": Object { + "script": Object { + "source": Any<String>, + }, + "type": "keyword", + }, + "slo.revision": Object { + "script": Object { + "source": "emit(1)", + }, + "type": "long", + }, + }, + }, + "sync": Object { + "time": Object { + "delay": "60s", + "field": "@timestamp", + }, + }, + "transform_id": Any<String>, +} +`; diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts index 05ef2ac431a83..35a9b5088ca70 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts @@ -5,11 +5,8 @@ * 2.0. */ -import { - AggregationsCalendarInterval, - MappingRuntimeFieldType, - TransformPutTransformRequest, -} from '@elastic/elasticsearch/lib/api/types'; +import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; +import { InvalidTransformError } from '../../../errors'; import { ALL_VALUE, apmTransactionDurationIndicatorSchema } from '../../../types/schema'; import { SLO_DESTINATION_INDEX_NAME, @@ -22,17 +19,17 @@ import { TransformGenerator } from '.'; const APM_SOURCE_INDEX = 'metrics-apm*'; -export class ApmTransactionDurationTransformGenerator implements TransformGenerator { +export class ApmTransactionDurationTransformGenerator extends TransformGenerator { public getTransformParams(slo: SLO): TransformPutTransformRequest { if (!apmTransactionDurationIndicatorSchema.is(slo.indicator)) { - throw new Error(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); + throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } return getSLOTransformTemplate( this.buildTransformId(slo), this.buildSource(slo, slo.indicator), this.buildDestination(), - this.buildGroupBy(), + this.buildCommonGroupBy(slo), this.buildAggregations(slo.indicator) ); } @@ -77,20 +74,7 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera return { index: APM_SOURCE_INDEX, - runtime_mappings: { - 'slo.id': { - type: 'keyword' as MappingRuntimeFieldType, - script: { - source: `emit('${slo.id}')`, - }, - }, - 'slo.revision': { - type: 'long' as MappingRuntimeFieldType, - script: { - source: `emit(${slo.revision})`, - }, - }, - }, + runtime_mappings: this.buildCommonRuntimeMappings(slo), query: { bool: { filter: [ @@ -113,27 +97,6 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera }; } - private buildGroupBy() { - return { - 'slo.id': { - terms: { - field: 'slo.id', - }, - }, - 'slo.revision': { - terms: { - field: 'slo.revision', - }, - }, - '@timestamp': { - date_histogram: { - field: '@timestamp', - calendar_interval: '1m' as AggregationsCalendarInterval, - }, - }, - }; - } - private buildAggregations(indicator: APMTransactionDurationIndicator) { const truncatedThreshold = Math.trunc(indicator.params['threshold.us']); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts index 3e69a4e371342..0da9f93e8d145 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts @@ -5,11 +5,8 @@ * 2.0. */ -import { - AggregationsCalendarInterval, - MappingRuntimeFieldType, - TransformPutTransformRequest, -} from '@elastic/elasticsearch/lib/api/types'; +import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; +import { InvalidTransformError } from '../../../errors'; import { ALL_VALUE, apmTransactionErrorRateIndicatorSchema } from '../../../types/schema'; import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; import { TransformGenerator } from '.'; @@ -24,17 +21,17 @@ const APM_SOURCE_INDEX = 'metrics-apm*'; const ALLOWED_STATUS_CODES = ['2xx', '3xx', '4xx', '5xx']; const DEFAULT_GOOD_STATUS_CODES = ['2xx', '3xx', '4xx']; -export class ApmTransactionErrorRateTransformGenerator implements TransformGenerator { +export class ApmTransactionErrorRateTransformGenerator extends TransformGenerator { public getTransformParams(slo: SLO): TransformPutTransformRequest { if (!apmTransactionErrorRateIndicatorSchema.is(slo.indicator)) { - throw new Error(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); + throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } return getSLOTransformTemplate( this.buildTransformId(slo), this.buildSource(slo, slo.indicator), this.buildDestination(), - this.buildGroupBy(), + this.buildCommonGroupBy(slo), this.buildAggregations(slo, slo.indicator) ); } @@ -79,20 +76,7 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener return { index: APM_SOURCE_INDEX, - runtime_mappings: { - 'slo.id': { - type: 'keyword' as MappingRuntimeFieldType, - script: { - source: `emit('${slo.id}')`, - }, - }, - 'slo.revision': { - type: 'long' as MappingRuntimeFieldType, - script: { - source: `emit(${slo.revision})`, - }, - }, - }, + runtime_mappings: this.buildCommonRuntimeMappings(slo), query: { bool: { filter: [ @@ -115,27 +99,6 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener }; } - private buildGroupBy() { - return { - 'slo.id': { - terms: { - field: 'slo.id', - }, - }, - 'slo.revision': { - terms: { - field: 'slo.revision', - }, - }, - '@timestamp': { - date_histogram: { - field: '@timestamp', - calendar_interval: '1m' as AggregationsCalendarInterval, - }, - }, - }; - } - private buildAggregations(slo: SLO, indicator: APMTransactionErrorRateIndicator) { const goodStatusCodesFilter = this.getGoodStatusCodesFilter(indicator.params.good_status_codes); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/index.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/index.ts index 6f0484c2044ad..08d6032a6eafa 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/index.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/index.ts @@ -8,3 +8,4 @@ export * from './transform_generator'; export * from './apm_transaction_error_rate'; export * from './apm_transaction_duration'; +export * from './kql_custom'; diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.test.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.test.ts new file mode 100644 index 0000000000000..46870fc8c7004 --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.test.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createKQLCustomIndicator, createSLO } from '../fixtures/slo'; +import { KQLCustomTransformGenerator } from './kql_custom'; + +const generator = new KQLCustomTransformGenerator(); + +describe('KQL Custom Transform Generator', () => { + describe('validation', () => { + it('throws when the KQL numerator is invalid', () => { + const anSLO = createSLO({ + indicator: createKQLCustomIndicator({ numerator: '{ kql.query: invalid' }), + }); + expect(() => generator.getTransformParams(anSLO)).toThrow(/Invalid KQL/); + }); + it('throws when the KQL denominator is invalid', () => { + const anSLO = createSLO({ + indicator: createKQLCustomIndicator({ denominator: '{ kql.query: invalid' }), + }); + expect(() => generator.getTransformParams(anSLO)).toThrow(/Invalid KQL/); + }); + it('throws when the KQL query_filter is invalid', () => { + const anSLO = createSLO({ + indicator: createKQLCustomIndicator({ query_filter: '{ kql.query: invalid' }), + }); + expect(() => generator.getTransformParams(anSLO)).toThrow(/Invalid KQL/); + }); + }); + + it('returns the correct transform params with every specified indicator params', async () => { + const anSLO = createSLO({ indicator: createKQLCustomIndicator() }); + const transform = generator.getTransformParams(anSLO); + + expect(transform).toMatchSnapshot({ + transform_id: expect.any(String), + source: { runtime_mappings: { 'slo.id': { script: { source: expect.any(String) } } } }, + }); + expect(transform.transform_id).toEqual(`slo-${anSLO.id}-${anSLO.revision}`); + expect(transform.source.runtime_mappings!['slo.id']).toMatchObject({ + script: { source: `emit('${anSLO.id}')` }, + }); + expect(transform.source.runtime_mappings!['slo.revision']).toMatchObject({ + script: { source: `emit(${anSLO.revision})` }, + }); + }); + + it('filters the source using the kql query', async () => { + const anSLO = createSLO({ + indicator: createKQLCustomIndicator({ query_filter: 'labels.groupId: group-4' }), + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform.source.query).toMatchSnapshot(); + }); + + it('uses the provided index', async () => { + const anSLO = createSLO({ + indicator: createKQLCustomIndicator({ index: 'my-own-index*' }), + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform.source.index).toBe('my-own-index*'); + }); + + it('aggregates using the numerator kql', async () => { + const anSLO = createSLO({ + indicator: createKQLCustomIndicator({ + numerator: + 'latency < 400 and (http.status_code: 2xx or http.status_code: 3xx or http.status_code: 4xx)', + }), + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform.pivot!.aggregations!['slo.numerator']).toMatchSnapshot(); + }); + + it('aggregates using the denominator kql', async () => { + const anSLO = createSLO({ + indicator: createKQLCustomIndicator({ + denominator: 'http.status_code: *', + }), + }); + const transform = generator.getTransformParams(anSLO); + + expect(transform.pivot!.aggregations!['slo.denominator']).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.ts new file mode 100644 index 0000000000000..c2228b6a3095c --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/kql_custom.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 { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; + +import { InvalidTransformError } from '../../../errors'; +import { kqlCustomIndicatorSchema } from '../../../types/schema'; +import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; +import { TransformGenerator } from '.'; +import { + SLO_DESTINATION_INDEX_NAME, + SLO_INGEST_PIPELINE_NAME, + getSLOTransformId, +} from '../../../assets/constants'; +import { KQLCustomIndicator, SLO } from '../../../types/models'; + +export class KQLCustomTransformGenerator extends TransformGenerator { + public getTransformParams(slo: SLO): TransformPutTransformRequest { + if (!kqlCustomIndicatorSchema.is(slo.indicator)) { + throw new InvalidTransformError(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); + } + + return getSLOTransformTemplate( + this.buildTransformId(slo), + this.buildSource(slo, slo.indicator), + this.buildDestination(), + this.buildCommonGroupBy(slo), + this.buildAggregations(slo, slo.indicator) + ); + } + + private buildTransformId(slo: SLO): string { + return getSLOTransformId(slo.id, slo.revision); + } + + private buildSource(slo: SLO, indicator: KQLCustomIndicator) { + const filter = getElastichsearchQueryOrThrow(indicator.params.query_filter); + return { + index: indicator.params.index, + runtime_mappings: this.buildCommonRuntimeMappings(slo), + query: filter, + }; + } + + private buildDestination() { + return { + pipeline: SLO_INGEST_PIPELINE_NAME, + index: SLO_DESTINATION_INDEX_NAME, + }; + } + + private buildAggregations(slo: SLO, indicator: KQLCustomIndicator) { + const numerator = getElastichsearchQueryOrThrow(indicator.params.numerator); + const denominator = getElastichsearchQueryOrThrow(indicator.params.denominator); + return { + 'slo.numerator': { + filter: numerator, + }, + 'slo.denominator': { + filter: denominator, + }, + }; + } +} + +function getElastichsearchQueryOrThrow(kuery: string) { + try { + return toElasticsearchQuery(fromKueryExpression(kuery)); + } catch (err) { + throw new InvalidTransformError(`Invalid KQL: ${kuery}`); + } +} diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts index 3965e809373c8..cd0ceaa602c22 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/transform_generator.ts @@ -5,9 +5,147 @@ * 2.0. */ -import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { MappingRuntimeFieldType } from '@elastic/elasticsearch/lib/api/types'; +import { + AggregationsCalendarInterval, + TransformPutTransformRequest, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + calendarAlignedTimeWindowSchema, + rollingTimeWindowSchema, + timeslicesBudgetingMethodSchema, +} from '../../../types/schema'; import { SLO } from '../../../types/models'; -export interface TransformGenerator { - getTransformParams(slo: SLO): TransformPutTransformRequest; +export abstract class TransformGenerator { + public abstract getTransformParams(slo: SLO): TransformPutTransformRequest; + + public buildCommonRuntimeMappings(slo: SLO) { + return { + 'slo.id': { + type: 'keyword' as MappingRuntimeFieldType, + script: { + source: `emit('${slo.id}')`, + }, + }, + 'slo.revision': { + type: 'long' as MappingRuntimeFieldType, + script: { + source: `emit(${slo.revision})`, + }, + }, + 'slo._internal.name': { + type: 'keyword' as MappingRuntimeFieldType, + script: { + source: `emit('${slo.name}')`, + }, + }, + 'slo._internal.budgeting_method': { + type: 'keyword' as MappingRuntimeFieldType, + script: { + source: `emit('${slo.budgeting_method}')`, + }, + }, + 'slo._internal.objective.target': { + type: 'double' as MappingRuntimeFieldType, + script: { + source: `emit(${slo.objective.target})`, + }, + }, + ...(timeslicesBudgetingMethodSchema.is(slo.budgeting_method) && { + 'slo._internal.objective.timeslice_target': { + type: 'double' as MappingRuntimeFieldType, + script: { + source: `emit(${slo.objective.timeslice_target})`, + }, + }, + 'slo._internal.objective.timeslice_window': { + type: 'keyword' as MappingRuntimeFieldType, + script: { + source: `emit('${slo.objective.timeslice_window?.format()}')`, + }, + }, + }), + 'slo._internal.time_window.duration': { + type: 'keyword' as MappingRuntimeFieldType, + script: { + source: `emit('${slo.time_window.duration.format()}')`, + }, + }, + ...(calendarAlignedTimeWindowSchema.is(slo.time_window) && { + 'slo._internal.time_window.is_rolling': { + type: 'boolean' as MappingRuntimeFieldType, + script: { + source: `emit(false)`, + }, + }, + }), + ...(rollingTimeWindowSchema.is(slo.time_window) && { + 'slo._internal.time_window.is_rolling': { + type: 'boolean' as MappingRuntimeFieldType, + script: { + source: `emit(true)`, + }, + }, + }), + }; + } + + public buildCommonGroupBy(slo: SLO) { + return { + 'slo.id': { + terms: { + field: 'slo.id', + }, + }, + 'slo.revision': { + terms: { + field: 'slo.revision', + }, + }, + 'slo._internal.name': { + terms: { + field: 'slo._internal.name', + }, + }, + 'slo._internal.budgeting_method': { + terms: { + field: 'slo._internal.budgeting_method', + }, + }, + 'slo._internal.objective.target': { + terms: { + field: 'slo._internal.objective.target', + }, + }, + 'slo._internal.time_window.duration': { + terms: { + field: 'slo._internal.time_window.duration', + }, + }, + 'slo._internal.time_window.is_rolling': { + terms: { + field: 'slo._internal.time_window.is_rolling', + }, + }, + ...(timeslicesBudgetingMethodSchema.is(slo.budgeting_method) && { + 'slo._internal.objective.timeslice_target': { + terms: { + field: 'slo._internal.objective.timeslice_target', + }, + }, + 'slo._internal.objective.timeslice_window': { + terms: { + field: 'slo._internal.objective.timeslice_window', + }, + }, + }), + '@timestamp': { + date_histogram: { + field: '@timestamp', + calendar_interval: '1m' as AggregationsCalendarInterval, + }, + }, + }; + } } diff --git a/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts b/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts index 1951888d734fd..696e04f5c13bc 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts @@ -138,13 +138,13 @@ describe('TransformManager', () => { }); }); -class DummyTransformGenerator implements TransformGenerator { +class DummyTransformGenerator extends TransformGenerator { getTransformParams(slo: SLO): TransformPutTransformRequest { return {} as TransformPutTransformRequest; } } -class FailTransformGenerator implements TransformGenerator { +class FailTransformGenerator extends TransformGenerator { getTransformParams(slo: SLO): TransformPutTransformRequest { throw new Error('Some error'); } diff --git a/x-pack/plugins/observability/server/types/models/duration.test.ts b/x-pack/plugins/observability/server/types/models/duration.test.ts index 4383c8e3ddd79..6f6b2dcb0c2d3 100644 --- a/x-pack/plugins/observability/server/types/models/duration.test.ts +++ b/x-pack/plugins/observability/server/types/models/duration.test.ts @@ -20,6 +20,18 @@ describe('Duration', () => { expect(() => new Duration(1, 'z' as DurationUnit)).toThrow('invalid duration unit'); }); + describe('format', () => { + it('formats the duration correctly', () => { + expect(new Duration(1, DurationUnit.m).format()).toBe('1m'); + expect(new Duration(1, DurationUnit.h).format()).toBe('1h'); + expect(new Duration(1, DurationUnit.d).format()).toBe('1d'); + expect(new Duration(1, DurationUnit.w).format()).toBe('1w'); + expect(new Duration(1, DurationUnit.M).format()).toBe('1M'); + expect(new Duration(1, DurationUnit.Q).format()).toBe('1Q'); + expect(new Duration(1, DurationUnit.Y).format()).toBe('1Y'); + }); + }); + describe('isShorterThan', () => { it('returns true when the current duration is shorter than the other duration', () => { const short = new Duration(1, DurationUnit.m); diff --git a/x-pack/plugins/observability/server/types/models/duration.ts b/x-pack/plugins/observability/server/types/models/duration.ts index e34a748e30ba6..aafed067f0bae 100644 --- a/x-pack/plugins/observability/server/types/models/duration.ts +++ b/x-pack/plugins/observability/server/types/models/duration.ts @@ -33,6 +33,10 @@ class Duration { const currentDurationMoment = moment.duration(this.value, toMomentUnitOfTime(this.unit)); return currentDurationMoment.asSeconds() < otherDurationMoment.asSeconds(); } + + format(): string { + return `${this.value}${this.unit}`; + } } const toMomentUnitOfTime = (unit: DurationUnit): moment.unitOfTime.Diff => { diff --git a/x-pack/plugins/observability/server/types/models/indicators.ts b/x-pack/plugins/observability/server/types/models/indicators.ts index 51ed07bb657d6..ea02059a03ff4 100644 --- a/x-pack/plugins/observability/server/types/models/indicators.ts +++ b/x-pack/plugins/observability/server/types/models/indicators.ts @@ -12,10 +12,12 @@ import { indicatorDataSchema, indicatorSchema, indicatorTypesSchema, + kqlCustomIndicatorSchema, } from '../schema'; type APMTransactionErrorRateIndicator = t.TypeOf<typeof apmTransactionErrorRateIndicatorSchema>; type APMTransactionDurationIndicator = t.TypeOf<typeof apmTransactionDurationIndicatorSchema>; +type KQLCustomIndicator = t.TypeOf<typeof kqlCustomIndicatorSchema>; type Indicator = t.TypeOf<typeof indicatorSchema>; type IndicatorTypes = t.TypeOf<typeof indicatorTypesSchema>; type IndicatorData = t.TypeOf<typeof indicatorDataSchema>; @@ -25,5 +27,6 @@ export type { IndicatorTypes, APMTransactionErrorRateIndicator, APMTransactionDurationIndicator, + KQLCustomIndicator, IndicatorData, }; diff --git a/x-pack/plugins/observability/server/types/schema/duration.ts b/x-pack/plugins/observability/server/types/schema/duration.ts index b4fa5065063f3..c7a3815140a6a 100644 --- a/x-pack/plugins/observability/server/types/schema/duration.ts +++ b/x-pack/plugins/observability/server/types/schema/duration.ts @@ -24,7 +24,7 @@ const durationType = new t.Type<Duration, string, unknown>( return t.failure(input, context); } }), - (duration: Duration): string => `${duration.value}${duration.unit}` + (duration: Duration): string => duration.format() ); export { durationType }; diff --git a/x-pack/plugins/observability/server/types/schema/indicators.ts b/x-pack/plugins/observability/server/types/schema/indicators.ts index 268ccdb570675..ab343e5b7b995 100644 --- a/x-pack/plugins/observability/server/types/schema/indicators.ts +++ b/x-pack/plugins/observability/server/types/schema/indicators.ts @@ -8,8 +8,7 @@ import * as t from 'io-ts'; import { allOrAnyString } from './common'; -const apmTransactionDurationIndicatorTypeSchema = t.literal<string>('slo.apm.transaction_duration'); - +const apmTransactionDurationIndicatorTypeSchema = t.literal('slo.apm.transaction_duration'); const apmTransactionDurationIndicatorSchema = t.type({ type: apmTransactionDurationIndicatorTypeSchema, params: t.type({ @@ -21,10 +20,7 @@ const apmTransactionDurationIndicatorSchema = t.type({ }), }); -const apmTransactionErrorRateIndicatorTypeSchema = t.literal<string>( - 'slo.apm.transaction_error_rate' -); - +const apmTransactionErrorRateIndicatorTypeSchema = t.literal('slo.apm.transaction_error_rate'); const apmTransactionErrorRateIndicatorSchema = t.type({ type: apmTransactionErrorRateIndicatorTypeSchema, params: t.intersection([ @@ -42,16 +38,29 @@ const apmTransactionErrorRateIndicatorSchema = t.type({ ]), }); +const kqlCustomIndicatorTypeSchema = t.literal('slo.kql.custom'); +const kqlCustomIndicatorSchema = t.type({ + type: kqlCustomIndicatorTypeSchema, + params: t.type({ + index: t.string, + query_filter: t.string, + numerator: t.string, + denominator: t.string, + }), +}); + const indicatorDataSchema = t.type({ good: t.number, total: t.number }); const indicatorTypesSchema = t.union([ apmTransactionDurationIndicatorTypeSchema, apmTransactionErrorRateIndicatorTypeSchema, + kqlCustomIndicatorTypeSchema, ]); const indicatorSchema = t.union([ apmTransactionDurationIndicatorSchema, apmTransactionErrorRateIndicatorSchema, + kqlCustomIndicatorSchema, ]); export { @@ -59,6 +68,8 @@ export { apmTransactionDurationIndicatorTypeSchema, apmTransactionErrorRateIndicatorSchema, apmTransactionErrorRateIndicatorTypeSchema, + kqlCustomIndicatorSchema, + kqlCustomIndicatorTypeSchema, indicatorSchema, indicatorTypesSchema, indicatorDataSchema, diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index a5b111569e311..f272db404b7b8 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -197,13 +197,13 @@ export const uiSettings: Record<string, UiSettings> = { [apmServiceInventoryOptimizedSorting]: { category: [observabilityFeatureId], name: i18n.translate('xpack.observability.apmServiceInventoryOptimizedSorting', { - defaultMessage: 'Optimize APM Service Inventory page load performance', + defaultMessage: 'Optimize services list load performance in APM', }), description: i18n.translate( 'xpack.observability.apmServiceInventoryOptimizedSortingDescription', { defaultMessage: - '{technicalPreviewLabel} Default APM Service Inventory page sort (for Services without Machine Learning applied) to sort by Service Name. {feedbackLink}.', + '{technicalPreviewLabel} Default APM Service Inventory and Storage Explorer pages sort (for Services without Machine Learning applied) to sort by Service Name. {feedbackLink}.', values: { technicalPreviewLabel: `<em>[${technicalPreviewLabel}]</em>`, feedbackLink: feedbackLink({ href: 'https://ela.st/feedback-apm-page-performance' }), diff --git a/x-pack/plugins/profiling/common/base64.ts b/x-pack/plugins/profiling/common/base64.ts new file mode 100644 index 0000000000000..0d724c142271a --- /dev/null +++ b/x-pack/plugins/profiling/common/base64.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. + */ + +export const safeBase64Decoder = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0, +]; + +export const safeBase64Encoder = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234456789-_'; + +/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ +export function charCodeAt(input: string, i: number): number { + return safeBase64Decoder[input.charCodeAt(i) & 0x7f]; +} diff --git a/x-pack/plugins/profiling/common/profiling.test.ts b/x-pack/plugins/profiling/common/profiling.test.ts index e51478bc62fc3..df014bada46bb 100644 --- a/x-pack/plugins/profiling/common/profiling.test.ts +++ b/x-pack/plugins/profiling/common/profiling.test.ts @@ -6,12 +6,23 @@ */ import { + createStackFrameID, createStackFrameMetadata, FrameType, + getAddressFromStackFrameID, getCalleeFunction, getCalleeSource, + getFileIDFromStackFrameID, } from './profiling'; +describe('Stack frame operations', () => { + test('decode stack frame ID', () => { + const frameID = createStackFrameID('ABCDEFGHIJKLMNOPQRSTUw', 123456789); + expect(getAddressFromStackFrameID(frameID)).toEqual(123456789); + expect(getFileIDFromStackFrameID(frameID)).toEqual('ABCDEFGHIJKLMNOPQRSTUw'); + }); +}); + describe('Stack frame metadata operations', () => { test('metadata has executable and function names', () => { const metadata = createStackFrameMetadata({ diff --git a/x-pack/plugins/profiling/common/profiling.ts b/x-pack/plugins/profiling/common/profiling.ts index 73c0e908ea27c..a7a5a890c6b5b 100644 --- a/x-pack/plugins/profiling/common/profiling.ts +++ b/x-pack/plugins/profiling/common/profiling.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { charCodeAt, safeBase64Encoder } from './base64'; + export type StackTraceID = string; export type StackFrameID = string; export type FileID = string; @@ -16,6 +18,37 @@ export function createStackFrameID(fileID: FileID, addressOrLine: number): Stack return buf.toString('base64url'); } +/* eslint no-bitwise: ["error", { "allow": ["&"] }] */ +export function getFileIDFromStackFrameID(frameID: StackFrameID): FileID { + return frameID.slice(0, 21) + safeBase64Encoder[frameID.charCodeAt(21) & 0x30]; +} + +/* eslint no-bitwise: ["error", { "allow": ["<<=", "&"] }] */ +export function getAddressFromStackFrameID(frameID: StackFrameID): number { + let address = charCodeAt(frameID, 21) & 0xf; + address <<= 6; + address += charCodeAt(frameID, 22); + address <<= 6; + address += charCodeAt(frameID, 23); + address <<= 6; + address += charCodeAt(frameID, 24); + address <<= 6; + address += charCodeAt(frameID, 25); + address <<= 6; + address += charCodeAt(frameID, 26); + address <<= 6; + address += charCodeAt(frameID, 27); + address <<= 6; + address += charCodeAt(frameID, 28); + address <<= 6; + address += charCodeAt(frameID, 29); + address <<= 6; + address += charCodeAt(frameID, 30); + address <<= 6; + address += charCodeAt(frameID, 31); + return address; +} + export enum FrameType { Unsymbolized = 0, Python, diff --git a/x-pack/plugins/profiling/common/run_length_encoding.test.ts b/x-pack/plugins/profiling/common/run_length_encoding.test.ts new file mode 100644 index 0000000000000..22f3b5141b0a0 --- /dev/null +++ b/x-pack/plugins/profiling/common/run_length_encoding.test.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 { runLengthDecode, runLengthDecodeBase64Url, runLengthEncode } from './run_length_encoding'; + +describe('Run-length encoding operations', () => { + test('run length is fully reversible', () => { + const tests: number[][] = [[], [0], [0, 1, 2, 3], [0, 1, 1, 2, 2, 2, 3, 3, 3, 3]]; + + for (const t of tests) { + expect(runLengthDecode(runLengthEncode(t))).toEqual(t); + } + }); + + test('runLengthDecode with optional parameter', () => { + const tests: Array<{ + bytes: Buffer; + expected: number[]; + }> = [ + { + bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), + expected: [0, 0, 0, 0, 0, 2, 2], + }, + { + bytes: Buffer.from([0x1, 0x8]), + expected: [8], + }, + ]; + + for (const t of tests) { + expect(runLengthDecode(t.bytes, t.expected.length)).toEqual(t.expected); + } + }); + + test('runLengthDecode with larger output than available input', () => { + const bytes = Buffer.from([0x5, 0x0, 0x2, 0x2]); + const decoded = [0, 0, 0, 0, 0, 2, 2]; + const expected = decoded.concat(Array(decoded.length).fill(0)); + + expect(runLengthDecode(bytes, expected.length)).toEqual(expected); + }); + + test('runLengthDecode without optional parameter', () => { + const tests: Array<{ + bytes: Buffer; + expected: number[]; + }> = [ + { + bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), + expected: [0, 0, 0, 0, 0, 2, 2], + }, + { + bytes: Buffer.from([0x1, 0x8]), + expected: [8], + }, + ]; + + for (const t of tests) { + expect(runLengthDecode(t.bytes)).toEqual(t.expected); + } + }); + + test('runLengthDecode works for very long runs', () => { + const tests: Array<{ + bytes: Buffer; + expected: number[]; + }> = [ + { + bytes: Buffer.from([0x5, 0x2, 0xff, 0x0]), + expected: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), + }, + { + bytes: Buffer.from([0xff, 0x2, 0x1, 0x2]), + expected: Array(256).fill(2), + }, + ]; + + for (const t of tests) { + expect(runLengthDecode(t.bytes)).toEqual(t.expected); + } + }); + + test('runLengthEncode works for very long runs', () => { + const tests: Array<{ + numbers: number[]; + expected: Buffer; + }> = [ + { + numbers: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), + expected: Buffer.from([0x5, 0x2, 0xff, 0x0]), + }, + { + numbers: Array(256).fill(2), + expected: Buffer.from([0xff, 0x2, 0x1, 0x2]), + }, + ]; + + for (const t of tests) { + expect(runLengthEncode(t.numbers)).toEqual(t.expected); + } + }); + + test('runLengthDecodeBase64Url', () => { + const tests: Array<{ + data: string; + expected: number[]; + }> = [ + { + data: 'CQM', + expected: [3, 3, 3, 3, 3, 3, 3, 3, 3], + }, + { + data: 'EgMHBA', + expected: Array(18).fill(3).concat(Array(7).fill(4)), + }, + { + data: 'CAMfBQIDEAQ', + expected: Array(8) + .fill(3) + .concat(Array(31).fill(5)) + .concat([3, 3]) + .concat(Array(16).fill(4)), + }, + ]; + + for (const t of tests) { + expect(runLengthDecodeBase64Url(t.data, t.data.length, t.expected.length)).toEqual( + t.expected + ); + } + }); + + test('runLengthDecodeBase64Url with larger output than available input', () => { + const data = Buffer.from([0x5, 0x0, 0x3, 0x2]).toString('base64url'); + const decoded = [0, 0, 0, 0, 0, 2, 2, 2]; + const expected = decoded.concat(Array(decoded.length).fill(0)); + + expect(runLengthDecodeBase64Url(data, data.length, expected.length)).toEqual(expected); + }); + + test('runLengthDecodeBase64Url works for very long runs', () => { + const tests: Array<{ + data: string; + expected: number[]; + }> = [ + { + data: Buffer.from([0x5, 0x2, 0xff, 0x0]).toString('base64url'), + expected: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), + }, + { + data: Buffer.from([0xff, 0x2, 0x1, 0x2]).toString('base64url'), + expected: Array(256).fill(2), + }, + ]; + + for (const t of tests) { + expect(runLengthDecodeBase64Url(t.data, t.data.length, t.expected.length)).toEqual( + t.expected + ); + } + }); +}); diff --git a/x-pack/plugins/profiling/common/run_length_encoding.ts b/x-pack/plugins/profiling/common/run_length_encoding.ts new file mode 100644 index 0000000000000..28a34a94acd57 --- /dev/null +++ b/x-pack/plugins/profiling/common/run_length_encoding.ts @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { charCodeAt } from './base64'; + +// runLengthEncode run-length encodes the input array. +// +// The input is a list of uint8s. The output is a binary stream of +// 2-byte pairs (first byte is the length and the second byte is the +// binary representation of the object) in reverse order. +// +// E.g. uint8 array [0, 0, 0, 0, 0, 2, 2, 2] is converted into the byte +// array [5, 0, 3, 2]. +export function runLengthEncode(input: number[]): Buffer { + const output: number[] = []; + + if (input.length === 0) { + return Buffer.from(output); + } + + let count = 1; + let current = input[0]; + + for (let i = 1; i < input.length; i++) { + const next = input[i]; + + if (next === current && count < 255) { + count++; + continue; + } + + output.push(count, current); + + count = 1; + current = next; + } + + output.push(count, current); + + return Buffer.from(output); +} + +function copyNumber(target: number[], value: number, offset: number, end: number) { + for (let i = offset; i < end; i++) { + target[i] = value; + } +} + +// runLengthDecode decodes a run-length encoding for the input array. +// +// The input is a binary stream of 2-byte pairs (first byte is the length and the +// second byte is the binary representation of the object). The output is a list of +// uint8s. +// +// E.g. byte array [5, 0, 3, 2] is converted into an uint8 array like +// [0, 0, 0, 0, 0, 2, 2, 2]. +export function runLengthDecode(input: Buffer, outputSize?: number): number[] { + let size; + + if (typeof outputSize === 'undefined') { + size = 0; + for (let i = 0; i < input.length; i += 2) { + size += input[i]; + } + } else { + size = outputSize; + } + + const output: number[] = new Array(size); + + let idx = 0; + for (let i = 0; i < input.length; i += 2) { + for (let j = 0; j < input[i]; j++) { + output[idx] = input[i + 1]; + idx++; + } + } + + // Due to truncation of the frame types for stacktraces longer than 255, + // the expected output size and the actual decoded size can be different. + // Ordinarily, these two values should be the same. + // + // We have decided to fill in the remainder of the output array with zeroes + // as a reasonable default. Without this step, the output array would have + // undefined values. + copyNumber(output, 0, idx, size); + + return output; +} + +// runLengthDecodeBase64Url decodes a run-length encoding for the +// base64-encoded input string. +// +// The input is a base64-encoded string. The output is a list of uint8s. +// +// E.g. string 'BQADAg' is converted into an uint8 array like +// [0, 0, 0, 0, 0, 2, 2, 2]. +// +// The motivating intent for this method is to unpack a base64-encoded +// run-length encoding without using intermediate storage. +// +// This method relies on these assumptions and details: +// - array encoded using run-length and base64 always returns string of length +// 0, 3, or 6 (mod 8) +// - since original array is composed of uint8s, we ignore Unicode codepoints +// - JavaScript bitwise operators operate on 32-bits so decoding must be done +// in 32-bit chunks + +/* eslint no-bitwise: ["error", { "allow": ["<<", ">>", ">>=", "&", "|"] }] */ +export function runLengthDecodeBase64Url(input: string, size: number, capacity: number): number[] { + const output = new Array<number>(capacity); + const multipleOf8 = Math.floor(size / 8); + const remainder = size % 8; + + let n = 0; + let count = 0; + let value = 0; + let i = 0; + let j = 0; + + for (i = 0; i < multipleOf8; i += 8) { + n = + (charCodeAt(input, i) << 26) | + (charCodeAt(input, i + 1) << 20) | + (charCodeAt(input, i + 2) << 14) | + (charCodeAt(input, i + 3) << 8) | + (charCodeAt(input, i + 4) << 2) | + (charCodeAt(input, i + 5) >> 4); + + count = (n >> 24) & 0xff; + value = (n >> 16) & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + + count = (n >> 8) & 0xff; + value = n & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + + n = + ((charCodeAt(input, i + 5) & 0xf) << 12) | + (charCodeAt(input, i + 6) << 6) | + charCodeAt(input, i + 7); + + count = (n >> 8) & 0xff; + value = n & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + } + + if (remainder === 6) { + n = + (charCodeAt(input, i) << 26) | + (charCodeAt(input, i + 1) << 20) | + (charCodeAt(input, i + 2) << 14) | + (charCodeAt(input, i + 3) << 8) | + (charCodeAt(input, i + 4) << 2) | + (charCodeAt(input, i + 5) >> 4); + + count = (n >> 24) & 0xff; + value = (n >> 16) & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + + count = (n >> 8) & 0xff; + value = n & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + } else if (remainder === 3) { + n = (charCodeAt(input, i) << 12) | (charCodeAt(input, i + 1) << 6) | charCodeAt(input, i + 2); + n >>= 2; + + count = (n >> 8) & 0xff; + value = n & 0xff; + + copyNumber(output, value, j, j + count); + j += count; + } + + // Due to truncation of the frame types for stacktraces longer than 255, + // the expected output size and the actual decoded size can be different. + // Ordinarily, these two values should be the same. + // + // We have decided to fill in the remainder of the output array with zeroes + // as a reasonable default. Without this step, the output array would have + // undefined values. + copyNumber(output, 0, j, capacity); + + return output; +} diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_number.ts b/x-pack/plugins/profiling/public/utils/formatters/as_number.ts index f7b67bafbf7f7..365cd876ad69e 100644 --- a/x-pack/plugins/profiling/public/utils/formatters/as_number.ts +++ b/x-pack/plugins/profiling/public/utils/formatters/as_number.ts @@ -11,18 +11,18 @@ export function asNumber(value: number): string { } value = Math.round(value * 100) / 100; - if (value < 0.01) { + if (Math.abs(value) < 0.01) { return '~0.00'; } - if (value < 1e3) { + if (Math.abs(value) < 1e3) { return value.toString(); } - if (value < 1e6) { + if (Math.abs(value) < 1e6) { return `${asNumber(value / 1e3)}k`; } - if (value < 1e9) { + if (Math.abs(value) < 1e9) { return `${asNumber(value / 1e6)}m`; } diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts index 5dd3f1985a35a..91b55312928c3 100644 --- a/x-pack/plugins/profiling/server/routes/stacktrace.test.ts +++ b/x-pack/plugins/profiling/server/routes/stacktrace.test.ts @@ -6,12 +6,8 @@ */ import { createStackFrameID, StackTrace } from '../../common/profiling'; -import { - decodeStackTrace, - EncodedStackTrace, - runLengthDecode, - runLengthEncode, -} from './stacktrace'; +import { runLengthEncode } from '../../common/run_length_encoding'; +import { decodeStackTrace, EncodedStackTrace } from './stacktrace'; enum fileID { A = 'aQpJmTLWydNvOapSFZOwKg', @@ -89,100 +85,4 @@ describe('Stack trace operations', () => { expect(decodeStackTrace(t.original)).toEqual(t.expected); } }); - - test('run length is fully reversible', () => { - const tests: number[][] = [[], [0], [0, 1, 2, 3], [0, 1, 1, 2, 2, 2, 3, 3, 3, 3]]; - - for (const t of tests) { - expect(runLengthDecode(runLengthEncode(t))).toEqual(t); - } - }); - - test('runLengthDecode with optional parameter', () => { - const tests: Array<{ - bytes: Buffer; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), - expected: [0, 0, 0, 0, 0, 2, 2], - }, - { - bytes: Buffer.from([0x1, 0x8]), - expected: [8], - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes, t.expected.length)).toEqual(t.expected); - } - }); - - test('runLengthDecode with larger output than available input', () => { - const bytes = Buffer.from([0x5, 0x0, 0x2, 0x2]); - const decoded = [0, 0, 0, 0, 0, 2, 2]; - const expected = decoded.concat(Array(decoded.length).fill(0)); - - expect(runLengthDecode(bytes, expected.length)).toEqual(expected); - }); - - test('runLengthDecode without optional parameter', () => { - const tests: Array<{ - bytes: Buffer; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x0, 0x2, 0x2]), - expected: [0, 0, 0, 0, 0, 2, 2], - }, - { - bytes: Buffer.from([0x1, 0x8]), - expected: [8], - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes)).toEqual(t.expected); - } - }); - - test('runLengthDecode works for very long runs', () => { - const tests: Array<{ - bytes: Buffer; - expected: number[]; - }> = [ - { - bytes: Buffer.from([0x5, 0x2, 0xff, 0x0]), - expected: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), - }, - { - bytes: Buffer.from([0xff, 0x2, 0x1, 0x2]), - expected: Array(256).fill(2), - }, - ]; - - for (const t of tests) { - expect(runLengthDecode(t.bytes)).toEqual(t.expected); - } - }); - - test('runLengthEncode works for very long runs', () => { - const tests: Array<{ - numbers: number[]; - expected: Buffer; - }> = [ - { - numbers: [2, 2, 2, 2, 2].concat(Array(255).fill(0)), - expected: Buffer.from([0x5, 0x2, 0xff, 0x0]), - }, - { - numbers: Array(256).fill(2), - expected: Buffer.from([0xff, 0x2, 0x1, 0x2]), - }, - ]; - - for (const t of tests) { - expect(runLengthEncode(t.numbers)).toEqual(t.expected); - } - }); }); diff --git a/x-pack/plugins/profiling/server/routes/stacktrace.ts b/x-pack/plugins/profiling/server/routes/stacktrace.ts index 0f513023fa829..8c5b668e25351 100644 --- a/x-pack/plugins/profiling/server/routes/stacktrace.ts +++ b/x-pack/plugins/profiling/server/routes/stacktrace.ts @@ -21,11 +21,14 @@ import { emptyStackFrame, Executable, FileID, + getAddressFromStackFrameID, + getFileIDFromStackFrameID, StackFrame, StackFrameID, StackTrace, StackTraceID, } from '../../common/profiling'; +import { runLengthDecodeBase64Url } from '../../common/run_length_encoding'; import { ProfilingESClient } from '../utils/create_profiling_es_client'; import { withProfilingSpan } from '../utils/with_profiling_span'; import { DownsampledEventsIndex } from './downsampling'; @@ -52,87 +55,6 @@ export type EncodedStackTrace = DedotObject<{ [ProfilingESField.StacktraceFrameTypes]: string; }>; -// runLengthEncode run-length encodes the input array. -// -// The input is a list of uint8s. The output is a binary stream of -// 2-byte pairs (first byte is the length and the second byte is the -// binary representation of the object) in reverse order. -// -// E.g. uint8 array [0, 0, 0, 0, 0, 2, 2, 2] is converted into the byte -// array [5, 0, 3, 2]. -export function runLengthEncode(input: number[]): Buffer { - const output: number[] = []; - - if (input.length === 0) { - return Buffer.from(output); - } - - let count = 1; - let current = input[0]; - - for (let i = 1; i < input.length; i++) { - const next = input[i]; - - if (next === current && count < 255) { - count++; - continue; - } - - output.push(count, current); - - count = 1; - current = next; - } - - output.push(count, current); - - return Buffer.from(output); -} - -// runLengthDecode decodes a run-length encoding for the input array. -// -// The input is a binary stream of 2-byte pairs (first byte is the length and the -// second byte is the binary representation of the object). The output is a list of -// uint8s. -// -// E.g. byte array [5, 0, 3, 2] is converted into an uint8 array like -// [0, 0, 0, 0, 0, 2, 2, 2]. -export function runLengthDecode(input: Buffer, outputSize?: number): number[] { - let size; - - if (typeof outputSize === 'undefined') { - size = 0; - for (let i = 0; i < input.length; i += 2) { - size += input[i]; - } - } else { - size = outputSize; - } - - const output: number[] = new Array(size); - - let idx = 0; - for (let i = 0; i < input.length; i += 2) { - for (let j = 0; j < input[i]; j++) { - output[idx] = input[i + 1]; - idx++; - } - } - - // Due to truncation of the frame types for stacktraces longer than 255, - // the expected output size and the actual decoded size can be different. - // Ordinarily, these two values should be the same. - // - // We have decided to fill in the remainder of the output array with zeroes - // as a reasonable default. Without this step, the output array would have - // undefined values. - for (let i = idx; i < size; i++) { - output[i] = 0; - } - - return output; -} - // decodeStackTrace unpacks an encoded stack trace from Elasticsearch export function decodeStackTrace(input: EncodedStackTrace): StackTrace { const inputFrameIDs = input.Stacktrace.frame.ids; @@ -152,19 +74,15 @@ export function decodeStackTrace(input: EncodedStackTrace): StackTrace { // However, since the file ID is base64-encoded using 21.33 bytes // (16 * 4 / 3), then the 22 bytes have an extra 4 bits from the // address (see diagram in definition of EncodedStackTrace). - for (let i = 0; i < countsFrameIDs; i++) { - const pos = i * BASE64_FRAME_ID_LENGTH; + for (let i = 0, pos = 0; i < countsFrameIDs; i++, pos += BASE64_FRAME_ID_LENGTH) { const frameID = inputFrameIDs.slice(pos, pos + BASE64_FRAME_ID_LENGTH); - const buf = Buffer.from(frameID, 'base64url'); - - fileIDs[i] = buf.toString('base64url', 0, 16); - addressOrLines[i] = Number(buf.readBigUInt64BE(16)); frameIDs[i] = frameID; + fileIDs[i] = getFileIDFromStackFrameID(frameID); + addressOrLines[i] = getAddressFromStackFrameID(frameID); } // Step 2: Convert the run-length byte encoding into a list of uint8s. - const types = Buffer.from(inputFrameTypes, 'base64url'); - const typeIDs = runLengthDecode(types, countsFrameIDs); + const typeIDs = runLengthDecodeBase64Url(inputFrameTypes, inputFrameTypes.length, countsFrameIDs); return { AddressOrLines: addressOrLines, diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js index 63367cfd6d001..4f404ee653496 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js @@ -34,7 +34,7 @@ describe('<RemoteClusterList />', () => { const { httpSetup, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js index 7a4eae8d0841f..a087cabe64d96 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_clone.test.js @@ -26,7 +26,7 @@ describe('Cloning a rollup job through create job wizard', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js index 565badc85403e..be3c1f09b6257 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js @@ -23,7 +23,7 @@ describe('Create Rollup Job, step 2: Date histogram', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js index 1166462a833de..af35dc9063c1a 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js @@ -23,7 +23,7 @@ describe('Create Rollup Job, step 4: Histogram', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js index 540b9b7bb0dbc..c22da0d2fa476 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js @@ -23,7 +23,7 @@ describe('Create Rollup Job, step 1: Logistics', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js index 9f8b108049e23..6e4ffea0367a9 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js @@ -23,7 +23,7 @@ describe('Create Rollup Job, step 5: Metrics', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js index 4927233b958b9..36d33d6ab1874 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js @@ -35,7 +35,7 @@ describe('Create Rollup Job, step 6: Review', () => { let component; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); }); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js index e4998d7e41ca7..6535b88311dbc 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js @@ -22,7 +22,7 @@ describe('Create Rollup Job, step 3: Terms', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js index ccb177c17a14b..5cd82054ce0e4 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js @@ -38,7 +38,7 @@ describe('<JobList />', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); initDocumentation(docLinksServiceMock.createStartContract()); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js index 03833cb3a02e8..20728477de7fc 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js @@ -31,7 +31,7 @@ describe('Smoke test cloning an existing rollup job from job list', () => { let startMock; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); startMock = coreMock.createStart(); setHttp(startMock.http); }); diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 160e06d03e92a..615201fe44d99 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -68,7 +68,8 @@ export interface LifecycleAlertServices< ActionGroupIds extends string = never > { alertWithLifecycle: LifecycleAlertService<InstanceState, InstanceContext, ActionGroupIds>; - getAlertStartedDate: (alertId: string) => string | null; + getAlertStartedDate: (alertInstanceId: string) => string | null; + getAlertUuid: (alertInstanceId: string) => string | null; } export type LifecycleRuleExecutor< @@ -88,6 +89,12 @@ export type LifecycleRuleExecutor< > ) => Promise<State | void>; +/* + `alertId` will at some point be renamed to `ruleId` as that more + accurately describes the meaning of the variable. + See https://github.com/elastic/kibana/issues/100115 +*/ + const trackedAlertStateRt = rt.type({ alertId: rt.string, alertUuid: rt.string, @@ -159,6 +166,8 @@ export const createLifecycleExecutor = const currentAlerts: Record<string, ExplicitAlertFields> = {}; + const newAlertUuids: Record<string, string> = {}; + const lifecycleAlertServices: LifecycleAlertServices< InstanceState, InstanceContext, @@ -169,6 +178,15 @@ export const createLifecycleExecutor = return alertFactory.create(id); }, getAlertStartedDate: (alertId: string) => state.trackedAlerts[alertId]?.started ?? null, + getAlertUuid: (alertId: string) => { + if (!state.trackedAlerts[alertId]) { + const alertUuid = v4(); + newAlertUuids[alertId] = alertUuid; + return alertUuid; + } + + return state.trackedAlerts[alertId].alertUuid; + }, }; const nextWrappedState = await wrappedExecutor({ @@ -203,9 +221,9 @@ export const createLifecycleExecutor = commonRuleFields ); result.forEach((hit) => { - const alertId = hit._source ? hit._source[ALERT_INSTANCE_ID] : void 0; - if (alertId && hit._source) { - trackedAlertsDataMap[alertId] = { + const alertInstanceId = hit._source ? hit._source[ALERT_INSTANCE_ID] : void 0; + if (alertInstanceId && hit._source) { + trackedAlertsDataMap[alertInstanceId] = { indexName: hit._index, fields: hit._source, }; @@ -226,10 +244,12 @@ export const createLifecycleExecutor = const isRecovered = !currentAlerts[alertId]; const isActive = !isRecovered; - const { alertUuid, started } = state.trackedAlerts[alertId] ?? { - alertUuid: v4(), - started: commonRuleFields[TIMESTAMP], - }; + const { alertUuid, started } = !isNew + ? state.trackedAlerts[alertId] + : { + alertUuid: newAlertUuids[alertId] || v4(), + started: commonRuleFields[TIMESTAMP], + }; const event: ParsedTechnicalFields & ParsedExperimentalFields = { ...alertData?.fields, @@ -249,8 +269,8 @@ export const createLifecycleExecutor = [ALERT_WORKFLOW_STATUS]: alertData?.fields[ALERT_WORKFLOW_STATUS] ?? 'open', [EVENT_KIND]: 'signal', [EVENT_ACTION]: isNew ? 'open' : isActive ? 'active' : 'close', - [VERSION]: ruleDataClient.kibanaVersion, [TAGS]: options.tags, + [VERSION]: ruleDataClient.kibanaVersion, ...(isRecovered ? { [ALERT_END]: commonRuleFields[TIMESTAMP] } : {}), }; diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index f71c7391cec77..132eb096e0aaa 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -40,6 +40,11 @@ function createRule(shouldWriteAlerts: boolean = true) { name: 'warning', }, ], + actionVariables: { + context: [], + params: [], + state: [], + }, defaultActionGroupId: 'warning', executor: async ({ services }) => { nextAlerts.forEach((alert) => { @@ -48,17 +53,17 @@ function createRule(shouldWriteAlerts: boolean = true) { nextAlerts = []; }, id: 'ruleTypeId', - minimumLicenseRequired: 'basic', isExportable: true, + minimumLicenseRequired: 'basic', name: 'ruleTypeName', producer: 'producer', - actionVariables: { - context: [], - params: [], - state: [], - }, validate: { - params: schema.object({}, { unknowns: 'allow' }), + params: schema.object( + {}, + { + unknowns: 'allow', + } + ), }, }); @@ -92,10 +97,12 @@ function createRule(shouldWriteAlerts: boolean = true) { state = ((await type.executor({ alertId: 'alertId', createdBy: 'createdBy', + executionId: 'b33f65d7-6e8b-4aae-8d20-c93613dec9f9', + logger: loggerMock.create(), name: 'name', + namespace: 'namespace', params: {}, previousStartedAt, - startedAt, rule: { actions: [], consumer: 'consumer', @@ -118,20 +125,18 @@ function createRule(shouldWriteAlerts: boolean = true) { services: { alertFactory, savedObjectsClient: {} as any, - uiSettingsClient: {} as any, scopedClusterClient: {} as any, - shouldWriteAlerts: () => shouldWriteAlerts, - shouldStopExecution: () => false, search: {} as any, searchSourceClient: {} as ISearchStartSearchSource, + shouldStopExecution: () => false, + shouldWriteAlerts: () => shouldWriteAlerts, + uiSettingsClient: {} as any, }, spaceId: 'spaceId', + startedAt, state, tags: ['tags'], updatedBy: 'updatedBy', - namespace: 'namespace', - executionId: 'b33f65d7-6e8b-4aae-8d20-c93613dec9f9', - logger: loggerMock.create(), })) ?? {}) as Record<string, any>; previousStartedAt = startedAt; diff --git a/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts b/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts index 5465e7a7922c5..a383110394da7 100644 --- a/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts +++ b/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts @@ -36,4 +36,5 @@ export const createLifecycleAlertServicesMock = < ): LifecycleAlertServices<InstanceState, InstanceContext, ActionGroupIds> => ({ alertWithLifecycle: ({ id }) => alertServices.alertFactory.create(id), getAlertStartedDate: jest.fn((id: string) => null), + getAlertUuid: jest.fn((id: string) => null), }); diff --git a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.test.tsx b/x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.test.tsx index 4a7f8d07db7e1..7dece57a4ea10 100644 --- a/x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.test.tsx +++ b/x-pack/plugins/runtime_fields/public/components/runtime_field_editor/runtime_field_editor.test.tsx @@ -37,7 +37,7 @@ describe('Runtime field editor', () => { const lastOnChangeCall = (): FormState[] => onChange.mock.calls[onChange.mock.calls.length - 1]; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts index 3cecb89889071..590d8668e4874 100644 --- a/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts +++ b/x-pack/plugins/screenshotting/server/browsers/chromium/driver.ts @@ -60,8 +60,8 @@ interface WaitForSelectorOpts { timeout: number; } -interface EvaluateOpts { - fn: EvaluateFunc<any>; +interface EvaluateOpts<A extends unknown[]> { + fn: EvaluateFunc<A>; args: unknown[]; } @@ -127,7 +127,7 @@ export class HeadlessChromiumDriver { this.interceptedCount = 0; /** - * Integrate with the screenshot mode plugin contract by calling this function before any other + * Integrate with the screenshot mode plugin contract by calling this function before whatever other * scripts have run on the browser page. */ await this.page.evaluateOnNewDocument(this.screenshotMode.setScreenshotModeEnabled); @@ -293,10 +293,14 @@ export class HeadlessChromiumDriver { return undefined; } - evaluate({ fn, args = [] }: EvaluateOpts, meta: EvaluateMetaOpts, logger: Logger): Promise<any> { + evaluate<A extends unknown[], T = void>( + { fn, args = [] }: EvaluateOpts<A>, + meta: EvaluateMetaOpts, + logger: Logger + ): Promise<T> { logger.debug(`evaluate ${meta.context}`); - return this.page.evaluate(fn, ...args); + return this.page.evaluate(fn as EvaluateFunc<unknown[]>, ...args) as Promise<T>; } public async waitForSelector( @@ -317,16 +321,20 @@ export class HeadlessChromiumDriver { return response; } - public async waitFor({ + public async waitFor<T extends unknown[] = unknown[]>({ fn, args, timeout, }: { - fn: EvaluateFunc<any>; + fn: EvaluateFunc<T>; args: unknown[]; timeout: number; }): Promise<void> { - await this.page.waitForFunction(fn, { timeout, polling: WAIT_FOR_DELAY_MS }, ...args); + await this.page.waitForFunction( + fn as EvaluateFunc<unknown[]>, + { timeout, polling: WAIT_FOR_DELAY_MS }, + ...args + ); } /** diff --git a/x-pack/plugins/screenshotting/server/screenshots/get_element_position_data.ts b/x-pack/plugins/screenshotting/server/screenshots/get_element_position_data.ts index 9d66f58c308d3..ff1c9c4d2bb3e 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/get_element_position_data.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/get_element_position_data.ts @@ -47,14 +47,18 @@ export const getElementPositionAndAttributes = async ( ); const { screenshot: screenshotSelector } = layout.selectors; // data-shared-items-container + const screenshotAttributes = { title: 'data-title', description: 'data-description' }; + let elementsPositionAndAttributes: ElementsPositionAndAttribute[] | null; try { - elementsPositionAndAttributes = await browser.evaluate( + elementsPositionAndAttributes = await browser.evaluate< + [typeof screenshotSelector, typeof screenshotAttributes], + ElementsPositionAndAttribute[] | null + >( { fn: (selector, attributes) => { const elements = Array.from(document.querySelectorAll<Element>(selector)); const results: ElementsPositionAndAttribute[] = []; - for (const element of elements) { const boundingClientRect = element.getBoundingClientRect() as DOMRect; results.push({ @@ -65,21 +69,18 @@ export const getElementPositionAndAttributes = async ( width: boundingClientRect.width, height: boundingClientRect.height, }, - scroll: { - x: window.scrollX, - y: window.scrollY, - }, + scroll: { x: window.scrollX, y: window.scrollY }, }, - attributes: Object.keys(attributes).reduce((result: AttributesMap, key) => { - const attribute = attributes[key]; + attributes: Object.keys(attributes).reduce<AttributesMap>((result, key) => { + const attribute = attributes[key as keyof typeof attributes]; result[key] = element.getAttribute(attribute); return result; - }, {} as AttributesMap), + }, {}), }); } return results; }, - args: [screenshotSelector, { title: 'data-title', description: 'data-description' }], + args: [screenshotSelector, screenshotAttributes], }, { context: CONTEXT_ELEMENTATTRIBUTES }, kbnLogger diff --git a/x-pack/plugins/screenshotting/server/screenshots/get_number_of_items.ts b/x-pack/plugins/screenshotting/server/screenshots/get_number_of_items.ts index 0e4da2fe5cf6a..ce84c9e213861 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/get_number_of_items.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/get_number_of_items.ts @@ -39,7 +39,7 @@ export const getNumberOfItems = async ( // returns the value of the `itemsCountAttribute` if it's there, otherwise // we just count the number of `itemSelector`: the number of items already rendered - itemsCount = await browser.evaluate( + itemsCount = await browser.evaluate<string[], number>( { fn: (selector, countAttribute) => { const elementWithCount = document.querySelector(`[${countAttribute}]`); diff --git a/x-pack/plugins/screenshotting/server/screenshots/get_render_errors.ts b/x-pack/plugins/screenshotting/server/screenshots/get_render_errors.ts index 44b92ceddbc8d..b18fc0dbff44f 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/get_render_errors.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/get_render_errors.ts @@ -25,7 +25,7 @@ export const getRenderErrors = async ( let errorsFound: undefined | string[]; try { - errorsFound = await browser.evaluate( + errorsFound = await browser.evaluate<string[], string[]>( { fn: (errorSelector, errorAttribute) => { const visualizations: Element[] = Array.from(document.querySelectorAll(errorSelector)); diff --git a/x-pack/plugins/screenshotting/server/screenshots/get_time_range.ts b/x-pack/plugins/screenshotting/server/screenshots/get_time_range.ts index f9272fd27ac95..85f284d368707 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/get_time_range.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/get_time_range.ts @@ -21,7 +21,7 @@ export const getTimeRange = async ( 'read' ); - const timeRange = await browser.evaluate( + const timeRange = await browser.evaluate<string[], string>( { fn: (durationAttribute) => { const durationElement = document.querySelector(`[${durationAttribute}]`); diff --git a/x-pack/plugins/screenshotting/server/screenshots/inject_css.ts b/x-pack/plugins/screenshotting/server/screenshots/inject_css.ts index 41426e893ce58..5afa4b6c62df0 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/inject_css.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/inject_css.ts @@ -34,7 +34,7 @@ export const injectCustomCss = async ( const buffer = await fsp.readFile(filePath); try { - await browser.evaluate( + await browser.evaluate<string[]>( { fn: (css) => { const node = document.createElement('style'); diff --git a/x-pack/plugins/screenshotting/server/screenshots/wait_for_render.ts b/x-pack/plugins/screenshotting/server/screenshots/wait_for_render.ts index ed4ad83736d42..74ac35e2f9148 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/wait_for_render.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/wait_for_render.ts @@ -21,7 +21,7 @@ export const waitForRenderComplete = async ( 'wait' ); - await browser.evaluate( + await browser.evaluate<string[]>( { fn: async (selector) => { const visualizations: NodeListOf<Element> = document.querySelectorAll(selector); diff --git a/x-pack/plugins/screenshotting/server/screenshots/wait_for_visualizations.ts b/x-pack/plugins/screenshotting/server/screenshots/wait_for_visualizations.ts index cf49fbe7dc798..15b02a92749b2 100644 --- a/x-pack/plugins/screenshotting/server/screenshots/wait_for_visualizations.ts +++ b/x-pack/plugins/screenshotting/server/screenshots/wait_for_visualizations.ts @@ -53,7 +53,7 @@ export const waitForVisualizations = async ( kbnLogger.debug(`waiting for ${toEqual} rendered elements to be in the DOM`); try { - await browser.waitFor({ + await browser.waitFor<CompletedItemsCountParameters[]>({ fn: getCompletedItemsCount, args: [{ renderCompleteSelector, context: CONTEXT_WAITFORELEMENTSTOBEINDOM, count: toEqual }], timeout, diff --git a/x-pack/plugins/security/public/analytics/analytics_service.test.ts b/x-pack/plugins/security/public/analytics/analytics_service.test.ts index be13fa25c1c8d..19643e68b4203 100644 --- a/x-pack/plugins/security/public/analytics/analytics_service.test.ts +++ b/x-pack/plugins/security/public/analytics/analytics_service.test.ts @@ -57,7 +57,7 @@ describe('AnalyticsService', () => { }); it('throttle reporting of the authentication type events', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const mockCore = coreMock.createStart(); mockCore.http.post.mockResolvedValue({ signature: 'some-signature', timestamp: 1234 }); diff --git a/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts b/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts index 282bf7beb68be..f33b8659fb27f 100644 --- a/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts +++ b/x-pack/plugins/security/public/management/roles/__fixtures__/kibana_features.ts @@ -226,4 +226,31 @@ export const kibanaFeatures = [ }, ], }), + createFeature({ + id: 'with_require_all_spaces_sub_features', + name: 'Require all spaces Sub Features', + subFeatures: [ + { + name: 'Require all spaces Sub Feature', + requireAllSpaces: true, + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + id: 'cool_toggle_1', + name: 'Cool toggle 1', + includeIn: 'read', + savedObject: { + all: [], + read: [], + }, + ui: ['cool_toggle_1-ui'], + }, + ], + }, + ], + }, + ], + }), ]; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx index af5ef0a0ccb00..7c81bb4a00de8 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx @@ -259,7 +259,7 @@ describe('field level security', () => { }); test('does not query for available fields when a request is already in flight', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const testProps = { ...props, diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx index f3b63bf32b5ff..7f5ac97e41edf 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx @@ -107,6 +107,10 @@ describe('FeatureTable', () => { primaryFeaturePrivilege: 'none', subFeaturePrivileges: [], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -157,6 +161,14 @@ describe('FeatureTable', () => { } : { subFeaturePrivileges: [] }), }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'all', + ...(canCustomizeSubFeaturePrivileges + ? { + subFeaturePrivileges: ['cool_toggle_1'], + } + : { subFeaturePrivileges: [] }), + }, }); }); @@ -208,6 +220,10 @@ describe('FeatureTable', () => { } : { subFeaturePrivileges: [] }), }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -302,6 +318,10 @@ describe('FeatureTable', () => { primaryFeaturePrivilege: 'read', subFeaturePrivileges: ['cool_all'], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -684,6 +704,10 @@ describe('FeatureTable', () => { 'cool_all', ], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -722,6 +746,10 @@ describe('FeatureTable', () => { primaryFeaturePrivilege: 'all', subFeaturePrivileges: [], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -760,6 +788,10 @@ describe('FeatureTable', () => { primaryFeaturePrivilege: 'read', subFeaturePrivileges: [], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); @@ -888,6 +920,10 @@ describe('FeatureTable', () => { primaryFeaturePrivilege: 'none', subFeaturePrivileges: [], }, + with_require_all_spaces_sub_features: { + primaryFeaturePrivilege: 'none', + subFeaturePrivileges: [], + }, }); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx index 505dd8e70024e..33f9f2879b4f7 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx @@ -250,6 +250,7 @@ export class FeatureTable extends Component<Props, State> { selectedFeaturePrivileges={ this.props.role.kibana[this.props.privilegeIndex].feature[feature.id] ?? [] } + allSpacesSelected={this.props.allSpacesSelected} disabled={this.props.disabled} licenseAllowsSubFeatPrivCustomization={ this.props.canCustomizeSubFeaturePrivileges diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx index 8e435dc43ef20..c7ab5a2be7890 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx @@ -49,6 +49,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['minimal_read']} onChange={jest.fn()} licenseAllowsSubFeatPrivCustomization={false} + allSpacesSelected={false} /> ); @@ -86,6 +87,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['none']} onChange={jest.fn()} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -118,6 +120,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['minimal_read']} onChange={jest.fn()} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -153,6 +156,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['read']} onChange={jest.fn()} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -186,6 +190,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['read']} onChange={jest.fn()} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -223,6 +228,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['read']} onChange={onChange} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -263,6 +269,7 @@ describe('FeatureTableExpandedRow', () => { selectedFeaturePrivileges={['minimal_read', 'cool_read', 'cool_toggle_2']} onChange={onChange} licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} /> ); @@ -272,4 +279,76 @@ describe('FeatureTableExpandedRow', () => { expect(onChange).toHaveBeenCalledWith('with_sub_features', ['read']); }); + + it('require all spaces enabled and allSpacesSelected is false: option is disabled', () => { + const role = createRole([ + { + base: [], + feature: { + with_require_all_spaces_sub_features: ['cool_toggle_1'], + }, + spaces: ['foo'], + }, + ]); + + const kibanaPrivileges = createKibanaPrivileges(kibanaFeatures); + const calculator = new PrivilegeFormCalculator(kibanaPrivileges, role); + const feature = kibanaPrivileges.getSecuredFeature('with_require_all_spaces_sub_features'); + const onChange = jest.fn(); + + const wrapper = mountWithIntl( + <FeatureTableExpandedRow + feature={feature} + privilegeIndex={0} + privilegeCalculator={calculator} + selectedFeaturePrivileges={['minimal_all']} + onChange={onChange} + licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={false} + /> + ); + + act(() => { + findTestSubject(wrapper, 'customizeSubFeaturePrivileges').simulate('click'); + }); + + const object = wrapper.find('SubFeatureForm'); + expect(object.props()).toMatchObject({ disabled: true }); + }); + + it('require all spaces enabled and allSpacesSelected is true: option is enabled', () => { + const role = createRole([ + { + base: [], + feature: { + with_require_all_spaces_sub_features: ['cool_toggle_1'], + }, + spaces: ['foo'], + }, + ]); + + const kibanaPrivileges = createKibanaPrivileges(kibanaFeatures); + const calculator = new PrivilegeFormCalculator(kibanaPrivileges, role); + const feature = kibanaPrivileges.getSecuredFeature('with_require_all_spaces_sub_features'); + const onChange = jest.fn(); + + const wrapper = mountWithIntl( + <FeatureTableExpandedRow + feature={feature} + privilegeIndex={0} + privilegeCalculator={calculator} + selectedFeaturePrivileges={['minimal_all']} + onChange={onChange} + licenseAllowsSubFeatPrivCustomization={true} + allSpacesSelected={true} + /> + ); + + act(() => { + findTestSubject(wrapper, 'customizeSubFeaturePrivileges').simulate('click'); + }); + + const object = wrapper.find('SubFeatureForm'); + expect(object.props()).toMatchObject({ disabled: false }); + }); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx index a0726ad2ef566..e5e28c4547374 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.tsx @@ -21,6 +21,7 @@ interface Props { privilegeCalculator: PrivilegeFormCalculator; privilegeIndex: number; selectedFeaturePrivileges: string[]; + allSpacesSelected: boolean; disabled?: boolean; licenseAllowsSubFeatPrivCustomization: boolean; onChange: (featureId: string, featurePrivileges: string[]) => void; @@ -32,6 +33,7 @@ export const FeatureTableExpandedRow = ({ privilegeIndex, privilegeCalculator, selectedFeaturePrivileges, + allSpacesSelected, disabled, licenseAllowsSubFeatPrivCustomization, }: Props) => { @@ -110,6 +112,8 @@ export const FeatureTableExpandedRow = ({ </div> </EuiFlexItem> {feature.getSubFeatures().map((subFeature) => { + const isDisabledDueToSpaceSelection = subFeature.requireAllSpaces && !allSpacesSelected; + return ( <EuiFlexItem key={subFeature.name}> <SubFeatureForm @@ -119,7 +123,7 @@ export const FeatureTableExpandedRow = ({ subFeature={subFeature} onChange={(updatedPrivileges) => onChange(feature.id, updatedPrivileges)} selectedFeaturePrivileges={selectedFeaturePrivileges} - disabled={disabled || !isCustomizing} + disabled={disabled || !isCustomizing || isDisabledDueToSpaceSelection} /> </EuiFlexItem> ); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx index d123773fed1c7..8af3bcdb2fef3 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx @@ -5,7 +5,14 @@ * 2.0. */ -import { EuiButtonGroup, EuiCheckbox, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { + EuiButtonGroup, + EuiCheckbox, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, + EuiText, +} from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; @@ -33,6 +40,27 @@ export const SubFeatureForm = (props: Props) => { .getPrivilegeGroups() .filter((group) => group.privileges.length > 0); + const getTooltip = () => { + if (!props.subFeature.privilegesTooltip) { + return null; + } + const tooltipContent = ( + <EuiText> + <p>{props.subFeature.privilegesTooltip}</p> + </EuiText> + ); + return ( + <EuiIconTip + iconProps={{ + className: 'eui-alignTop', + }} + type="iInCircle" + color="subdued" + content={tooltipContent} + /> + ); + }; + if (groupsWithPrivileges.length === 0) { return null; } @@ -40,7 +68,9 @@ export const SubFeatureForm = (props: Props) => { return ( <EuiFlexGroup> <EuiFlexItem> - <EuiText size="s">{props.subFeature.name}</EuiText> + <EuiText size="s"> + {props.subFeature.name} {getTooltip()} + </EuiText> </EuiFlexItem> <EuiFlexItem>{groupsWithPrivileges.map(renderPrivilegeGroup)}</EuiFlexItem> </EuiFlexGroup> diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.test.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.test.ts index a6714cb7a2d83..61be3af6eb1c8 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.test.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_calculator.test.ts @@ -53,6 +53,11 @@ describe('PrivilegeSummaryCalculator', () => { primary: undefined, subFeature: [], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: undefined, + subFeature: [], + }, }); }); @@ -99,6 +104,13 @@ describe('PrivilegeSummaryCalculator', () => { }), subFeature: ['cool_all', 'cool_read', 'cool_toggle_1', 'cool_toggle_2'], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: expect.objectContaining({ + id: 'all', + }), + subFeature: ['cool_toggle_1'], + }, }); }); @@ -155,6 +167,13 @@ describe('PrivilegeSummaryCalculator', () => { 'cool_excluded_toggle', ], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: expect.objectContaining({ + id: 'all', + }), + subFeature: ['cool_toggle_1'], + }, }); }); @@ -214,6 +233,13 @@ describe('PrivilegeSummaryCalculator', () => { 'cool_excluded_toggle', ], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: expect.objectContaining({ + id: 'all', + }), + subFeature: ['cool_toggle_1'], + }, }); }); @@ -255,6 +281,13 @@ describe('PrivilegeSummaryCalculator', () => { }), subFeature: ['cool_all', 'cool_read', 'cool_toggle_1', 'cool_toggle_2'], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: expect.objectContaining({ + id: 'all', + }), + subFeature: ['cool_toggle_1'], + }, }); }); @@ -294,6 +327,11 @@ describe('PrivilegeSummaryCalculator', () => { primary: undefined, subFeature: [], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: undefined, + subFeature: [], + }, }); }); @@ -333,6 +371,11 @@ describe('PrivilegeSummaryCalculator', () => { primary: undefined, subFeature: [], }, + with_require_all_spaces_sub_features: { + hasCustomizedSubFeaturePrivileges: false, + primary: undefined, + subFeature: [], + }, }); }); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx index d54866c88f7ef..62ad9bc83b1cc 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx @@ -90,6 +90,15 @@ const expectNoPrivileges = (displayedPrivileges: any, expectSubFeatures: boolean }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'None', + ...maybeExpectSubFeaturePrivileges(expectSubFeatures, { + 'Require all spaces Sub Feature': [], + }), + }, + }, }); }; @@ -248,6 +257,15 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'All', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -310,6 +328,15 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'All', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -370,6 +397,15 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'All', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -432,6 +468,15 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'None', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': [], + }), + }, + }, }); }); @@ -522,6 +567,22 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'All', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -614,6 +675,22 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -706,6 +783,22 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'None', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': [], + }), + }, + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); @@ -800,6 +893,22 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'None', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': [], + }), + }, + 'default, space-1': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'None', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': [], + }), + }, + }, }); }); @@ -925,6 +1034,29 @@ describe('PrivilegeSummaryTable', () => { }), }, }, + with_require_all_spaces_sub_features: { + '*': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + default: { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'All', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + 'space-1, space-2': { + hasCustomizedSubFeaturePrivileges: false, + primaryFeaturePrivilege: 'Read', + ...maybeExpectSubFeaturePrivileges(allowSubFeaturePrivileges, { + 'Require all spaces Sub Feature': ['Cool toggle 1'], + }), + }, + }, }); }); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx index 8f10acb2403aa..71876eeed963d 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx @@ -79,6 +79,10 @@ describe('PrivilegeSpaceForm', () => { "primaryFeaturePrivilege": "none", "subFeaturePrivileges": Array [], }, + "with_require_all_spaces_sub_features": Object { + "primaryFeaturePrivilege": "none", + "subFeaturePrivileges": Array [], + }, "with_sub_features": Object { "primaryFeaturePrivilege": "none", "subFeaturePrivileges": Array [], @@ -129,6 +133,12 @@ describe('PrivilegeSpaceForm', () => { "primaryFeaturePrivilege": "all", "subFeaturePrivileges": Array [], }, + "with_require_all_spaces_sub_features": Object { + "primaryFeaturePrivilege": "all", + "subFeaturePrivileges": Array [ + "cool_toggle_1", + ], + }, "with_sub_features": Object { "primaryFeaturePrivilege": "all", "subFeaturePrivileges": Array [ @@ -185,6 +195,10 @@ describe('PrivilegeSpaceForm', () => { "primaryFeaturePrivilege": "none", "subFeaturePrivileges": Array [], }, + "with_require_all_spaces_sub_features": Object { + "primaryFeaturePrivilege": "none", + "subFeaturePrivileges": Array [], + }, "with_sub_features": Object { "primaryFeaturePrivilege": "read", "subFeaturePrivileges": Array [ @@ -286,6 +300,10 @@ describe('PrivilegeSpaceForm', () => { "primaryFeaturePrivilege": "none", "subFeaturePrivileges": Array [], }, + "with_require_all_spaces_sub_features": Object { + "primaryFeaturePrivilege": "none", + "subFeaturePrivileges": Array [], + }, "with_sub_features": Object { "primaryFeaturePrivilege": "read", "subFeaturePrivileges": Array [ @@ -346,6 +364,7 @@ describe('PrivilegeSpaceForm', () => { with_excluded_sub_features: ['read'], no_sub_features: ['read'], with_sub_features: ['read'], + with_require_all_spaces_sub_features: ['read'], }, spaces: ['foo'], }, @@ -451,6 +470,7 @@ describe('PrivilegeSpaceForm', () => { with_excluded_sub_features: ['read'], no_sub_features: ['read'], with_sub_features: ['read'], + with_require_all_spaces_sub_features: ['read'], }, spaces: ['foo'], }, @@ -493,6 +513,7 @@ describe('PrivilegeSpaceForm', () => { no_sub_features: ['all'], no_sub_features_disabled_read: ['all'], with_sub_features: ['all'], + with_require_all_spaces_sub_features: ['all'], }, spaces: ['foo'], }, @@ -569,6 +590,7 @@ describe('PrivilegeSpaceForm', () => { no_sub_features: ['read'], no_sub_features_require_all_space: ['read'], with_sub_features: ['read'], + with_require_all_spaces_sub_features: ['read'], }, spaces: ['foo'], }, @@ -613,6 +635,7 @@ describe('PrivilegeSpaceForm', () => { with_excluded_sub_features: ['all'], no_sub_features: ['all'], with_sub_features: ['all'], + with_require_all_spaces_sub_features: ['all'], }, spaces: ['foo'], }, @@ -671,6 +694,7 @@ describe('PrivilegeSpaceForm', () => { no_sub_features: ['all'], no_sub_features_require_all_space: ['all'], with_sub_features: ['all'], + with_require_all_spaces_sub_features: ['all'], }, spaces: ['*'], }, diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx index a26b9587d450a..d0120d64fa5ed 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx @@ -24,6 +24,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; +import { remove } from 'lodash'; import React, { Component, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; @@ -472,9 +473,22 @@ export class PrivilegeSpaceForm extends Component<Props, State> { const primaryFeaturePrivilege = securedFeature ?.getPrimaryFeaturePrivileges({ includeMinimalFeaturePrivileges: true }) .find((pfp) => privileges.includes(pfp.id)) ?? { disabled: false, requireAllSpaces: false }; + + const areAllSpacesSelected = selectedSpaceIds.includes(ALL_SPACES_ID); + if (securedFeature) { + securedFeature.getSubFeatures().forEach((subFeature) => { + subFeature.privileges.forEach((currentPrivilege) => { + if (privileges.includes(currentPrivilege.id)) { + if (subFeature.requireAllSpaces && !areAllSpacesSelected) { + remove(privileges, (privilege) => privilege === currentPrivilege.id); + } + } + }); + }); + } const newFeaturePrivileges = primaryFeaturePrivilege?.disabled || - (primaryFeaturePrivilege?.requireAllSpaces && !selectedSpaceIds.includes(ALL_SPACES_ID)) + (primaryFeaturePrivilege?.requireAllSpaces && !areAllSpacesSelected) ? [] // The primary feature privilege cannot be selected; remove that and any selected sub-feature privileges, too : privileges; return { @@ -536,7 +550,12 @@ export class PrivilegeSpaceForm extends Component<Props, State> { newPrivileges = [nextFeaturePrivilege.id]; feature.getSubFeaturePrivileges().forEach((psf) => { if (Array.isArray(privileges) && privileges.includes(psf.id)) { - newPrivileges.push(psf.id); + if ( + !psf.requireAllSpaces || + (psf.requireAllSpaces && this.state.selectedSpaceIds.includes(ALL_SPACES_ID)) + ) { + newPrivileges.push(psf.id); + } } }); } diff --git a/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts b/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts index 1f76b17a39e59..8c312ee7ea772 100644 --- a/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts +++ b/x-pack/plugins/security/public/management/roles/model/secured_sub_feature.ts @@ -13,6 +13,7 @@ import { SubFeaturePrivilegeGroup } from './sub_feature_privilege_group'; export class SecuredSubFeature extends SubFeature { public readonly privileges: SubFeaturePrivilege[]; + public readonly privilegesTooltip: string; constructor( config: SubFeatureConfig, @@ -20,6 +21,8 @@ export class SecuredSubFeature extends SubFeature { ) { super(config); + this.privilegesTooltip = config.privilegesTooltip || ''; + this.privileges = []; for (const privilege of this.privilegeIterator()) { this.privileges.push(privilege); diff --git a/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege.ts b/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege.ts index 45a51a45533c1..b5897654a6a38 100644 --- a/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege.ts +++ b/x-pack/plugins/security/public/management/roles/model/sub_feature_privilege.ts @@ -20,4 +20,8 @@ export class SubFeaturePrivilege extends KibanaPrivilege { public get name() { return this.subPrivilegeConfig.name; } + + public get requireAllSpaces() { + return this.subPrivilegeConfig.requireAllSpaces ?? false; + } } diff --git a/x-pack/plugins/security/public/session/session_timeout.test.ts b/x-pack/plugins/security/public/session/session_timeout.test.ts index e43c1af6ac9c7..41e8907390296 100644 --- a/x-pack/plugins/security/public/session/session_timeout.test.ts +++ b/x-pack/plugins/security/public/session/session_timeout.test.ts @@ -25,7 +25,7 @@ import type { SessionInfo } from '../../common/types'; import { createSessionExpiredMock } from './session_expired.mock'; import { SessionTimeout, startTimer } from './session_timeout'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); jest.spyOn(window, 'addEventListener'); jest.spyOn(window, 'removeEventListener'); diff --git a/x-pack/plugins/security/server/audit/audit_service.test.ts b/x-pack/plugins/security/server/audit/audit_service.test.ts index dfd42c2260c5e..2cef6908355d0 100644 --- a/x-pack/plugins/security/server/audit/audit_service.test.ts +++ b/x-pack/plugins/security/server/audit/audit_service.test.ts @@ -25,7 +25,7 @@ import { RECORD_USAGE_INTERVAL, } from './audit_service'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); const logger = loggingSystemMock.createLogger(); const license = licenseMock.create(); diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts index cd18a28e0d373..ceb0dab8a7f70 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts @@ -233,6 +233,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/snooze", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkEdit", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkDelete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unsnooze", ] `); @@ -332,6 +333,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/snooze", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkEdit", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkDelete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unsnooze", "alerting:1.0.0-zeta1:alert-type/my-feature/alert/get", "alerting:1.0.0-zeta1:alert-type/my-feature/alert/find", @@ -391,6 +393,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/snooze", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkEdit", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkDelete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unsnooze", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/get", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getRuleState", @@ -499,6 +502,7 @@ describe(`feature_privilege_builder`, () => { "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/snooze", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkEdit", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/bulkDelete", "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unsnooze", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/get", "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getRuleState", diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts index a11a4fa77bcdd..19dcd2d3a38cb 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts @@ -42,6 +42,7 @@ const writeOperations: Record<AlertingEntity, string[]> = { 'unmuteAlert', 'snooze', 'bulkEdit', + 'bulkDelete', 'unsnooze', ], alert: ['update'], diff --git a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts index 219158bdda472..224ffeab0a0f4 100644 --- a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts +++ b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts @@ -88,7 +88,7 @@ describe('ElasticsearchService', () => { }); it('`watchOnlineStatus$` allows to schedule retry', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // Both ES and license are available. mockLicense.isEnabled.mockReturnValue(true); @@ -146,7 +146,7 @@ describe('ElasticsearchService', () => { }); it('`watchOnlineStatus$` cancels scheduled retry if status changes before retry timeout fires', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // Both ES and license are available. mockLicense.isEnabled.mockReturnValue(true); diff --git a/x-pack/plugins/security/server/lib/role_utils.ts b/x-pack/plugins/security/server/lib/role_utils.ts index c852079081821..3ec3247bf61a7 100644 --- a/x-pack/plugins/security/server/lib/role_utils.ts +++ b/x-pack/plugins/security/server/lib/role_utils.ts @@ -101,6 +101,22 @@ export const validateKibanaPrivileges = ( } } + kibanaFeature.subFeatures.forEach((subFeature) => { + if ( + subFeature.requireAllSpaces && + !forAllSpaces && + subFeature.privilegeGroups.some((group) => + group.privileges.some((privilege) => feature.includes(privilege.id)) + ) + ) { + errors.push( + `Sub-feature privilege [${kibanaFeature.name} - ${ + subFeature.name + }] requires all spaces to be selected but received [${priv.spaces.join(',')}]` + ); + } + }); + return errors; }); }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts index 842a3b74853b7..ed1648c069d4a 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.test.ts @@ -466,4 +466,86 @@ describe('validateKibanaPrivileges', () => { `Feature [foo] does not support privilege [read].`, ]); }); + + const fooSubFeature = new KibanaFeature({ + id: 'foo', + name: 'Foo', + privileges: { + all: { + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + read: { + disabled: true, + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, + subFeatures: [ + { + name: 'Require All Spaces Enabled', + requireAllSpaces: true, + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + id: 'test', + name: 'foo', + includeIn: 'none', + ui: ['test-ui'], + savedObject: { + all: [], + read: [], + }, + }, + ], + }, + ], + }, + ], + app: [], + category: { id: 'foo', label: 'foo' }, + }); + + test('returns no error when subfeature requireAllSpaces enabled and all spaces selected', () => { + expect( + validateKibanaPrivileges( + [fooSubFeature], + [ + { + spaces: ['*'], + base: [], + feature: { + foo: ['all', 'test'], + }, + }, + ] + ).validationErrors + ).toEqual([]); + }); + test('returns error when subfeature requireAllSpaces enabled but not all spaces selected', () => { + expect( + validateKibanaPrivileges( + [fooSubFeature], + [ + { + spaces: ['foo-space'], + base: [], + feature: { + foo: ['all', 'test'], + }, + }, + ] + ).validationErrors + ).toEqual([ + 'Sub-feature privilege [Foo - Require All Spaces Enabled] requires all spaces to be selected but received [foo-space]', + ]); + }); }); diff --git a/x-pack/plugins/security/server/user_profile/user_profile_service.test.ts b/x-pack/plugins/security/server/user_profile/user_profile_service.test.ts index 76d90f23a2e83..5b5a602d99ec3 100644 --- a/x-pack/plugins/security/server/user_profile/user_profile_service.test.ts +++ b/x-pack/plugins/security/server/user_profile/user_profile_service.test.ts @@ -405,7 +405,7 @@ describe('UserProfileService', () => { }); it('retries activation if initially fails with 409 error', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const failureReason = new errors.ResponseError( securityMock.createApiResponse({ statusCode: 409, body: 'some message' }) @@ -449,7 +449,7 @@ describe('UserProfileService', () => { }); it('fails if activation max retries exceeded', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const failureReason = new errors.ResponseError( securityMock.createApiResponse({ statusCode: 409, body: 'some message' }) diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 7b2c4e034a963..65c5757a1b1bb 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -260,16 +260,12 @@ export const UPDATE_OR_CREATE_LEGACY_ACTIONS = '/internal/api/detection/legacy/n * Detection engine routes */ export const DETECTION_ENGINE_URL = '/api/detection_engine' as const; -export const DETECTION_ENGINE_RULES_URL = `${DETECTION_ENGINE_URL}/rules` as const; -export const DETECTION_ENGINE_PREPACKAGED_URL = - `${DETECTION_ENGINE_RULES_URL}/prepackaged` as const; export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges` as const; export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index` as const; +export const DETECTION_ENGINE_RULES_URL = `${DETECTION_ENGINE_URL}/rules` as const; export const DETECTION_ENGINE_RULES_URL_FIND = `${DETECTION_ENGINE_RULES_URL}/_find` as const; export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags` as const; -export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = - `${DETECTION_ENGINE_RULES_URL}/prepackaged/_status` as const; export const DETECTION_ENGINE_RULES_BULK_ACTION = `${DETECTION_ENGINE_RULES_URL}/_bulk_action` as const; export const DETECTION_ENGINE_RULES_PREVIEW = `${DETECTION_ENGINE_RULES_URL}/preview` as const; @@ -300,13 +296,9 @@ export const RISK_SCORE_DELETE_STORED_SCRIPT = `${INTERNAL_RISK_SCORE_URL}/store * Internal detection engine routes */ export const INTERNAL_DETECTION_ENGINE_URL = '/internal/detection_engine' as const; -export const INTERNAL_DETECTION_ENGINE_RULES_URL = '/internal/detection_engine/rules' as const; -export const DETECTION_ENGINE_INSTALLED_INTEGRATIONS_URL = - `${INTERNAL_DETECTION_ENGINE_URL}/fleet/integrations/installed` as const; export const DETECTION_ENGINE_ALERTS_INDEX_URL = `${INTERNAL_DETECTION_ENGINE_URL}/signal/index` as const; -export const DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL = - `${INTERNAL_DETECTION_ENGINE_RULES_URL}/exceptions/_find_references` as const; + /** * Telemetry detection endpoint for any previews requested of what data we are * providing through UI/UX and for e2e tests. @@ -456,6 +448,7 @@ export const NEW_FEATURES_TOUR_STORAGE_KEYS = { export const RULE_DETAILS_EXECUTION_LOG_TABLE_SHOW_METRIC_COLUMNS_STORAGE_KEY = 'securitySolution.ruleDetails.ruleExecutionLog.showMetrics.v8.2'; +// TODO: https://github.com/elastic/kibana/pull/142950 /** * Error codes that can be thrown during _bulk_action API dry_run call and be processed and displayed to end user */ diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/get_installed_integrations_response_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/fleet_integrations/api/get_installed_integrations/response_schema.ts similarity index 80% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/get_installed_integrations_response_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/fleet_integrations/api/get_installed_integrations/response_schema.ts index 882fc3d81828a..d970fb7061a44 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/get_installed_integrations_response_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/fleet_integrations/api/get_installed_integrations/response_schema.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { InstalledIntegrationArray } from '../common'; +import type { InstalledIntegrationArray } from '../../model/installed_integrations'; export interface GetInstalledIntegrationsResponse { installed_integrations: InstalledIntegrationArray; diff --git a/x-pack/plugins/security_solution/common/detection_engine/fleet_integrations/api/urls.ts b/x-pack/plugins/security_solution/common/detection_engine/fleet_integrations/api/urls.ts new file mode 100644 index 0000000000000..b1216d855284e --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/fleet_integrations/api/urls.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 { INTERNAL_DETECTION_ENGINE_URL as INTERNAL_URL } from '../../../constants'; + +export const GET_INSTALLED_INTEGRATIONS_URL = + `${INTERNAL_URL}/fleet/integrations/installed` as const; diff --git a/x-pack/plugins/security_solution/common/detection_engine/fleet_integrations/index.ts b/x-pack/plugins/security_solution/common/detection_engine/fleet_integrations/index.ts new file mode 100644 index 0000000000000..e79ebecbdb1cd --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/fleet_integrations/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 * from './api/get_installed_integrations/response_schema'; +export * from './api/urls'; + +export * from './model/installed_integrations'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/installed_integrations.ts b/x-pack/plugins/security_solution/common/detection_engine/fleet_integrations/model/installed_integrations.ts similarity index 100% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/common/installed_integrations.ts rename to x-pack/plugins/security_solution/common/detection_engine/fleet_integrations/model/installed_integrations.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/response_schema.test.ts similarity index 78% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/response_schema.test.ts index 8997ecadb36b6..e721e6f41cc19 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/response_schema.test.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import type { PrePackagedRulesAndTimelinesStatusSchema } from './prepackaged_rules_status_schema'; -import { prePackagedRulesAndTimelinesStatusSchema } from './prepackaged_rules_status_schema'; +import { pipe } from 'fp-ts/lib/pipeable'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -describe('prepackaged_rules_schema', () => { +import { GetPrebuiltRulesAndTimelinesStatusResponse } from './response_schema'; + +describe('Get prebuilt rules and timelines status response schema', () => { test('it should validate an empty prepackaged response with defaults', () => { - const payload: PrePackagedRulesAndTimelinesStatusSchema = { + const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { rules_installed: 0, rules_not_installed: 0, rules_not_updated: 0, @@ -22,7 +22,7 @@ describe('prepackaged_rules_schema', () => { timelines_not_installed: 0, timelines_not_updated: 0, }; - const decoded = prePackagedRulesAndTimelinesStatusSchema.decode(payload); + const decoded = GetPrebuiltRulesAndTimelinesStatusResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -31,7 +31,7 @@ describe('prepackaged_rules_schema', () => { }); test('it should not validate an extra invalid field added', () => { - const payload: PrePackagedRulesAndTimelinesStatusSchema & { invalid_field: string } = { + const payload: GetPrebuiltRulesAndTimelinesStatusResponse & { invalid_field: string } = { rules_installed: 0, rules_not_installed: 0, rules_not_updated: 0, @@ -41,7 +41,7 @@ describe('prepackaged_rules_schema', () => { timelines_not_installed: 0, timelines_not_updated: 0, }; - const decoded = prePackagedRulesAndTimelinesStatusSchema.decode(payload); + const decoded = GetPrebuiltRulesAndTimelinesStatusResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -50,7 +50,7 @@ describe('prepackaged_rules_schema', () => { }); test('it should NOT validate an empty prepackaged response with a negative "rules_installed" number', () => { - const payload: PrePackagedRulesAndTimelinesStatusSchema = { + const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { rules_installed: -1, rules_not_installed: 0, rules_not_updated: 0, @@ -59,7 +59,7 @@ describe('prepackaged_rules_schema', () => { timelines_not_installed: 0, timelines_not_updated: 0, }; - const decoded = prePackagedRulesAndTimelinesStatusSchema.decode(payload); + const decoded = GetPrebuiltRulesAndTimelinesStatusResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -70,7 +70,7 @@ describe('prepackaged_rules_schema', () => { }); test('it should NOT validate an empty prepackaged response with a negative "rules_not_installed"', () => { - const payload: PrePackagedRulesAndTimelinesStatusSchema = { + const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { rules_installed: 0, rules_not_installed: -1, rules_not_updated: 0, @@ -79,7 +79,7 @@ describe('prepackaged_rules_schema', () => { timelines_not_installed: 0, timelines_not_updated: 0, }; - const decoded = prePackagedRulesAndTimelinesStatusSchema.decode(payload); + const decoded = GetPrebuiltRulesAndTimelinesStatusResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -90,7 +90,7 @@ describe('prepackaged_rules_schema', () => { }); test('it should NOT validate an empty prepackaged response with a negative "rules_not_updated"', () => { - const payload: PrePackagedRulesAndTimelinesStatusSchema = { + const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { rules_installed: 0, rules_not_installed: 0, rules_not_updated: -1, @@ -99,7 +99,7 @@ describe('prepackaged_rules_schema', () => { timelines_not_installed: 0, timelines_not_updated: 0, }; - const decoded = prePackagedRulesAndTimelinesStatusSchema.decode(payload); + const decoded = GetPrebuiltRulesAndTimelinesStatusResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -110,7 +110,7 @@ describe('prepackaged_rules_schema', () => { }); test('it should NOT validate an empty prepackaged response with a negative "rules_custom_installed"', () => { - const payload: PrePackagedRulesAndTimelinesStatusSchema = { + const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { rules_installed: 0, rules_not_installed: 0, rules_not_updated: 0, @@ -119,7 +119,7 @@ describe('prepackaged_rules_schema', () => { timelines_not_installed: 0, timelines_not_updated: 0, }; - const decoded = prePackagedRulesAndTimelinesStatusSchema.decode(payload); + const decoded = GetPrebuiltRulesAndTimelinesStatusResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -130,7 +130,7 @@ describe('prepackaged_rules_schema', () => { }); test('it should NOT validate an empty prepackaged response if "rules_installed" is not there', () => { - const payload: PrePackagedRulesAndTimelinesStatusSchema = { + const payload: GetPrebuiltRulesAndTimelinesStatusResponse = { rules_installed: 0, rules_not_installed: 0, rules_not_updated: 0, @@ -141,7 +141,7 @@ describe('prepackaged_rules_schema', () => { }; // @ts-expect-error delete payload.rules_installed; - const decoded = prePackagedRulesAndTimelinesStatusSchema.decode(payload); + const decoded = GetPrebuiltRulesAndTimelinesStatusResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); diff --git a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/response_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/response_schema.ts new file mode 100644 index 0000000000000..f5c26b81682a1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/response_schema.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 * as t from 'io-ts'; +import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; + +export type GetPrebuiltRulesAndTimelinesStatusResponse = t.TypeOf< + typeof GetPrebuiltRulesAndTimelinesStatusResponse +>; +export const GetPrebuiltRulesAndTimelinesStatusResponse = t.exact( + t.type({ + rules_custom_installed: PositiveInteger, + rules_installed: PositiveInteger, + rules_not_installed: PositiveInteger, + rules_not_updated: PositiveInteger, + + timelines_installed: PositiveInteger, + timelines_not_installed: PositiveInteger, + timelines_not_updated: PositiveInteger, + }) +); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/response_schema.test.ts similarity index 76% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/response_schema.test.ts index 393a071d21b32..6833a5891a2c2 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/response_schema.test.ts @@ -5,21 +5,21 @@ * 2.0. */ -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import type { PrePackagedRulesAndTimelinesSchema } from './prepackaged_rules_schema'; -import { prePackagedRulesAndTimelinesSchema } from './prepackaged_rules_schema'; +import { pipe } from 'fp-ts/lib/pipeable'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -describe('prepackaged_rules_schema', () => { +import { InstallPrebuiltRulesAndTimelinesResponse } from './response_schema'; + +describe('Install prebuilt rules and timelines response schema', () => { test('it should validate an empty prepackaged response with defaults', () => { - const payload: PrePackagedRulesAndTimelinesSchema = { + const payload: InstallPrebuiltRulesAndTimelinesResponse = { rules_installed: 0, rules_updated: 0, timelines_installed: 0, timelines_updated: 0, }; - const decoded = prePackagedRulesAndTimelinesSchema.decode(payload); + const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -28,14 +28,14 @@ describe('prepackaged_rules_schema', () => { }); test('it should not validate an extra invalid field added', () => { - const payload: PrePackagedRulesAndTimelinesSchema & { invalid_field: string } = { + const payload: InstallPrebuiltRulesAndTimelinesResponse & { invalid_field: string } = { rules_installed: 0, rules_updated: 0, invalid_field: 'invalid', timelines_installed: 0, timelines_updated: 0, }; - const decoded = prePackagedRulesAndTimelinesSchema.decode(payload); + const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -44,13 +44,13 @@ describe('prepackaged_rules_schema', () => { }); test('it should NOT validate an empty prepackaged response with a negative "rules_installed" number', () => { - const payload: PrePackagedRulesAndTimelinesSchema = { + const payload: InstallPrebuiltRulesAndTimelinesResponse = { rules_installed: -1, rules_updated: 0, timelines_installed: 0, timelines_updated: 0, }; - const decoded = prePackagedRulesAndTimelinesSchema.decode(payload); + const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -61,13 +61,13 @@ describe('prepackaged_rules_schema', () => { }); test('it should NOT validate an empty prepackaged response with a negative "rules_updated"', () => { - const payload: PrePackagedRulesAndTimelinesSchema = { + const payload: InstallPrebuiltRulesAndTimelinesResponse = { rules_installed: 0, rules_updated: -1, timelines_installed: 0, timelines_updated: 0, }; - const decoded = prePackagedRulesAndTimelinesSchema.decode(payload); + const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -78,7 +78,7 @@ describe('prepackaged_rules_schema', () => { }); test('it should NOT validate an empty prepackaged response if "rules_installed" is not there', () => { - const payload: PrePackagedRulesAndTimelinesSchema = { + const payload: InstallPrebuiltRulesAndTimelinesResponse = { rules_installed: 0, rules_updated: 0, timelines_installed: 0, @@ -86,7 +86,7 @@ describe('prepackaged_rules_schema', () => { }; // @ts-expect-error delete payload.rules_installed; - const decoded = prePackagedRulesAndTimelinesSchema.decode(payload); + const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -97,7 +97,7 @@ describe('prepackaged_rules_schema', () => { }); test('it should NOT validate an empty prepackaged response if "rules_updated" is not there', () => { - const payload: PrePackagedRulesAndTimelinesSchema = { + const payload: InstallPrebuiltRulesAndTimelinesResponse = { rules_installed: 0, rules_updated: 0, timelines_installed: 0, @@ -105,7 +105,7 @@ describe('prepackaged_rules_schema', () => { }; // @ts-expect-error delete payload.rules_updated; - const decoded = prePackagedRulesAndTimelinesSchema.decode(payload); + const decoded = InstallPrebuiltRulesAndTimelinesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); diff --git a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/response_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/response_schema.ts new file mode 100644 index 0000000000000..7da2d6b1fae03 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/response_schema.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 * as t from 'io-ts'; +import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; + +export type InstallPrebuiltRulesAndTimelinesResponse = t.TypeOf< + typeof InstallPrebuiltRulesAndTimelinesResponse +>; +export const InstallPrebuiltRulesAndTimelinesResponse = t.exact( + t.type({ + rules_installed: PositiveInteger, + rules_updated: PositiveInteger, + + timelines_installed: PositiveInteger, + timelines_updated: PositiveInteger, + }) +); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/urls.ts similarity index 53% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/urls.ts index 0472c62228c4d..449960916f239 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/urls.ts @@ -5,9 +5,7 @@ * 2.0. */ -import * as t from 'io-ts'; +import { DETECTION_ENGINE_RULES_URL as RULES } from '../../../constants'; -import { patchRulesSchema } from './patch_rules_schema'; - -export const patchRulesBulkSchema = t.array(patchRulesSchema); -export type PatchRulesBulkSchema = t.TypeOf<typeof patchRulesBulkSchema>; +export const PREBUILT_RULES_URL = `${RULES}/prepackaged` as const; +export const PREBUILT_RULES_STATUS_URL = `${RULES}/prepackaged/_status` as const; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/index.ts similarity index 53% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_bulk_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/index.ts index e877737885925..5ec8f617f1546 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_bulk_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/index.ts @@ -5,9 +5,8 @@ * 2.0. */ -import * as t from 'io-ts'; +export * from './api/get_prebuilt_rules_and_timelines_status/response_schema'; +export * from './api/install_prebuilt_rules_and_timelines/response_schema'; +export * from './api/urls'; -import { createRulesSchema } from './rule_schemas'; - -export const createRulesBulkSchema = t.array(createRulesSchema); -export type CreateRulesBulkSchema = t.TypeOf<typeof createRulesBulkSchema>; +export * from './model/prebuilt_rule'; diff --git a/x-pack/plugins/canvas/public/components/es_index_select/index.tsx b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/mocks.ts similarity index 64% rename from x-pack/plugins/canvas/public/components/es_index_select/index.tsx rename to x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/mocks.ts index 29a19e8770606..2850442ce975c 100644 --- a/x-pack/plugins/canvas/public/components/es_index_select/index.tsx +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/mocks.ts @@ -5,5 +5,4 @@ * 2.0. */ -export { ESIndexSelect } from './es_index_select'; -export { ESIndexSelect as ESIndexSelectComponent } from './es_index_select.component'; +export * from './model/prebuilt_rule.mock'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule.mock.ts similarity index 83% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts rename to x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule.mock.ts index 841e0aa5a13da..d81f23cde9966 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { AddPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; +import type { PrebuiltRuleToInstall } from './prebuilt_rule'; -export const getAddPrepackagedRulesSchemaMock = (): AddPrepackagedRulesSchema => ({ +export const getPrebuiltRuleMock = (): PrebuiltRuleToInstall => ({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', @@ -19,7 +19,7 @@ export const getAddPrepackagedRulesSchemaMock = (): AddPrepackagedRulesSchema => version: 1, }); -export const getAddPrepackagedThreatMatchRulesSchemaMock = (): AddPrepackagedRulesSchema => ({ +export const getPrebuiltThreatMatchRuleMock = (): PrebuiltRuleToInstall => ({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule.test.ts similarity index 75% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule.test.ts index 091ce8bd10fa3..4d08c9391fab8 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule.test.ts @@ -5,23 +5,20 @@ * 2.0. */ -import type { AddPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; -import { addPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; - -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { - getAddPrepackagedRulesSchemaMock, - getAddPrepackagedThreatMatchRulesSchemaMock, -} from './add_prepackaged_rules_schema.mock'; -import { getListArrayMock } from '../types/lists.mock'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { getListArrayMock } from '../../schemas/types/lists.mock'; + +import { PrebuiltRuleToInstall } from './prebuilt_rule'; +import { getPrebuiltRuleMock, getPrebuiltThreatMatchRuleMock } from './prebuilt_rule.mock'; -describe('add prepackaged rules schema', () => { +describe('Prebuilt rule schema', () => { test('empty objects do not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = {}; + const payload: Partial<PrebuiltRuleToInstall> = {}; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -46,12 +43,12 @@ describe('add prepackaged rules schema', () => { }); test('made up values do not validate', () => { - const payload: AddPrepackagedRulesSchema & { madeUp: string } = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall & { madeUp: string } = { + ...getPrebuiltRuleMock(), madeUp: 'hi', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); @@ -59,11 +56,11 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id] does not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -85,12 +82,12 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description] does not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', description: 'some description', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -109,13 +106,13 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from] does not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -134,14 +131,14 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to] does not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', to: 'now', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -160,7 +157,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, name] does not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -168,7 +165,7 @@ describe('add prepackaged rules schema', () => { name: 'some-name', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -184,7 +181,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, name, severity] does not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -193,7 +190,7 @@ describe('add prepackaged rules schema', () => { severity: 'low', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -206,7 +203,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type] does not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -216,7 +213,7 @@ describe('add prepackaged rules schema', () => { type: 'query', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -227,7 +224,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -238,7 +235,7 @@ describe('add prepackaged rules schema', () => { type: 'query', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -249,7 +246,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -261,7 +258,7 @@ describe('add prepackaged rules schema', () => { index: ['index-1'], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -272,7 +269,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, query, index, interval, version] does validate', () => { - const payload: AddPrepackagedRulesSchema = { + const payload: PrebuiltRuleToInstall = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -287,14 +284,14 @@ describe('add prepackaged rules schema', () => { version: 1, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -309,7 +306,7 @@ describe('add prepackaged rules schema', () => { risk_score: 50, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -319,7 +316,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, version] does validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -335,14 +332,14 @@ describe('add prepackaged rules schema', () => { version: 1, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does not validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> & { output_index: string } = { + const payload: Partial<PrebuiltRuleToInstall> & { output_index: string } = { rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -358,7 +355,7 @@ describe('add prepackaged rules schema', () => { language: 'kuery', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -368,7 +365,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, version] does validate', () => { - const payload: Partial<AddPrepackagedRulesSchema> = { + const payload: Partial<PrebuiltRuleToInstall> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -382,38 +379,38 @@ describe('add prepackaged rules schema', () => { version: 1, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can send in a namespace', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), namespace: 'a namespace', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can send in an empty array to threat', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), threat: [], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threat] does validate', () => { - const payload: AddPrepackagedRulesSchema = { + const payload: PrebuiltRuleToInstall = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -444,31 +441,31 @@ describe('add prepackaged rules schema', () => { version: 1, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('allows references to be sent as valid', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), references: ['index-1'], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('immutable cannot be set in a pre-packaged rule', () => { - const payload: AddPrepackagedRulesSchema & { immutable: boolean } = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall & { immutable: boolean } = { + ...getPrebuiltRuleMock(), immutable: true, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['invalid keys "immutable"']); @@ -476,11 +473,11 @@ describe('add prepackaged rules schema', () => { }); test('rule_id is required', () => { - const payload: AddPrepackagedRulesSchema = getAddPrepackagedRulesSchemaMock(); + const payload: PrebuiltRuleToInstall = getPrebuiltRuleMock(); // @ts-expect-error delete payload.rule_id; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -490,12 +487,12 @@ describe('add prepackaged rules schema', () => { }); test('references cannot be numbers', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'references'> & { references: number[] } = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall, 'references'> & { references: number[] } = { + ...getPrebuiltRuleMock(), references: [5], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "references"']); @@ -503,12 +500,12 @@ describe('add prepackaged rules schema', () => { }); test('indexes cannot be numbers', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'index'> & { index: number[] } = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall, 'index'> & { index: number[] } = { + ...getPrebuiltRuleMock(), index: [5], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "index"']); @@ -517,23 +514,23 @@ describe('add prepackaged rules schema', () => { test('saved_query type can have filters with it', () => { const payload = { - ...getAddPrepackagedRulesSchemaMock(), + ...getPrebuiltRuleMock(), filters: [], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('filters cannot be a string', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'filters'> & { filters: string } = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall, 'filters'> & { filters: string } = { + ...getPrebuiltRuleMock(), filters: 'some string', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -544,11 +541,11 @@ describe('add prepackaged rules schema', () => { test('language validates with kuery', () => { const payload = { - ...getAddPrepackagedRulesSchemaMock(), + ...getPrebuiltRuleMock(), language: 'kuery', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -556,23 +553,23 @@ describe('add prepackaged rules schema', () => { test('language validates with lucene', () => { const payload = { - ...getAddPrepackagedRulesSchemaMock(), + ...getPrebuiltRuleMock(), language: 'lucene', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('language does not validate with something made up', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'language'> & { language: string } = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall, 'language'> & { language: string } = { + ...getPrebuiltRuleMock(), language: 'something-made-up', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -582,12 +579,12 @@ describe('add prepackaged rules schema', () => { }); test('max_signals cannot be negative', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), max_signals: -1, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -597,12 +594,12 @@ describe('add prepackaged rules schema', () => { }); test('max_signals cannot be zero', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), max_signals: 0, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "0" supplied to "max_signals"']); @@ -610,36 +607,36 @@ describe('add prepackaged rules schema', () => { }); test('max_signals can be 1', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), max_signals: 1, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can optionally send in an array of tags', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), tags: ['tag_1', 'tag_2'], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot send in an array of tags that are numbers', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'tags'> & { tags: number[] } = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall, 'tags'> & { tags: number[] } = { + ...getPrebuiltRuleMock(), tags: [0, 1, 2], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -651,10 +648,10 @@ describe('add prepackaged rules schema', () => { }); test('You cannot send in an array of threat that are missing "framework"', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'threat'> & { - threat: Array<Partial<Omit<AddPrepackagedRulesSchema['threat'], 'framework'>>>; + const payload: Omit<PrebuiltRuleToInstall, 'threat'> & { + threat: Array<Partial<Omit<PrebuiltRuleToInstall['threat'], 'framework'>>>; } = { - ...getAddPrepackagedRulesSchemaMock(), + ...getPrebuiltRuleMock(), threat: [ { tactic: { @@ -673,7 +670,7 @@ describe('add prepackaged rules schema', () => { ], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -683,10 +680,10 @@ describe('add prepackaged rules schema', () => { }); test('You cannot send in an array of threat that are missing "tactic"', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'threat'> & { - threat: Array<Partial<Omit<AddPrepackagedRulesSchema['threat'], 'tactic'>>>; + const payload: Omit<PrebuiltRuleToInstall, 'threat'> & { + threat: Array<Partial<Omit<PrebuiltRuleToInstall['threat'], 'tactic'>>>; } = { - ...getAddPrepackagedRulesSchemaMock(), + ...getPrebuiltRuleMock(), threat: [ { framework: 'fake', @@ -701,7 +698,7 @@ describe('add prepackaged rules schema', () => { ], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -711,10 +708,10 @@ describe('add prepackaged rules schema', () => { }); test('You can send in an array of threat that are missing "technique"', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'threat'> & { - threat: Array<Partial<Omit<AddPrepackagedRulesSchema['threat'], 'technique'>>>; + const payload: Omit<PrebuiltRuleToInstall, 'threat'> & { + threat: Array<Partial<Omit<PrebuiltRuleToInstall['threat'], 'technique'>>>; } = { - ...getAddPrepackagedRulesSchemaMock(), + ...getPrebuiltRuleMock(), threat: [ { framework: 'fake', @@ -727,33 +724,33 @@ describe('add prepackaged rules schema', () => { ], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can optionally send in an array of false positives', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), false_positives: ['false_1', 'false_2'], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot send in an array of false positives that are numbers', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'false_positives'> & { + const payload: Omit<PrebuiltRuleToInstall, 'false_positives'> & { false_positives: number[]; } = { - ...getAddPrepackagedRulesSchemaMock(), + ...getPrebuiltRuleMock(), false_positives: [5, 4], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -763,12 +760,12 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); test('You cannot set the risk_score to 101', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), risk_score: 101, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -778,12 +775,12 @@ describe('add prepackaged rules schema', () => { }); test('You cannot set the risk_score to -1', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), risk_score: -1, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "-1" supplied to "risk_score"']); @@ -791,50 +788,50 @@ describe('add prepackaged rules schema', () => { }); test('You can set the risk_score to 0', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), risk_score: 0, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can set the risk_score to 100', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), risk_score: 100, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can set meta to any object you want', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), meta: { somethingMadeUp: { somethingElse: true }, }, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot create meta as a string', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'meta'> & { meta: string } = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall, 'meta'> & { meta: string } = { + ...getPrebuiltRuleMock(), meta: 'should not work', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -844,25 +841,25 @@ describe('add prepackaged rules schema', () => { }); test('validates with timeline_id and timeline_title', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), timeline_id: 'timeline-id', timeline_title: 'timeline-title', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot set the severity to a value other than low, medium, high, or critical', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'severity'> & { severity: string } = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall, 'severity'> & { severity: string } = { + ...getPrebuiltRuleMock(), severity: 'junk', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "junk" supplied to "severity"']); @@ -870,12 +867,12 @@ describe('add prepackaged rules schema', () => { }); test('You cannot send in an array of actions that are missing "group"', () => { - const payload: Omit<AddPrepackagedRulesSchema['actions'], 'group'> = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall['actions'], 'group'> = { + ...getPrebuiltRuleMock(), actions: [{ id: 'id', action_type_id: 'action_type_id', params: {} }], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -885,12 +882,12 @@ describe('add prepackaged rules schema', () => { }); test('You cannot send in an array of actions that are missing "id"', () => { - const payload: Omit<AddPrepackagedRulesSchema['actions'], 'id'> = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall['actions'], 'id'> = { + ...getPrebuiltRuleMock(), actions: [{ group: 'group', action_type_id: 'action_type_id', params: {} }], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -900,12 +897,12 @@ describe('add prepackaged rules schema', () => { }); test('You cannot send in an array of actions that are missing "action_type_id"', () => { - const payload: Omit<AddPrepackagedRulesSchema['actions'], 'action_type_id'> = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall['actions'], 'action_type_id'> = { + ...getPrebuiltRuleMock(), actions: [{ group: 'group', id: 'id', params: {} }], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -915,12 +912,12 @@ describe('add prepackaged rules schema', () => { }); test('You cannot send in an array of actions that are missing "params"', () => { - const payload: Omit<AddPrepackagedRulesSchema['actions'], 'params'> = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall['actions'], 'params'> = { + ...getPrebuiltRuleMock(), actions: [{ group: 'group', id: 'id', action_type_id: 'action_type_id' }], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -930,8 +927,8 @@ describe('add prepackaged rules schema', () => { }); test('You cannot send in an array of actions that are including "actionTypeId"', () => { - const payload: Omit<AddPrepackagedRulesSchema['actions'], 'actions'> = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall['actions'], 'actions'> = { + ...getPrebuiltRuleMock(), actions: [ { group: 'group', @@ -942,7 +939,7 @@ describe('add prepackaged rules schema', () => { ], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -953,38 +950,38 @@ describe('add prepackaged rules schema', () => { describe('note', () => { test('You can set note to a string', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), note: '# documentation markdown here', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can set note to an empty string', () => { - const payload: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), note: '', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot create note as an object', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'note'> & { note: {} } = { - ...getAddPrepackagedRulesSchemaMock(), + const payload: Omit<PrebuiltRuleToInstall, 'note'> & { note: {} } = { + ...getPrebuiltRuleMock(), note: { somethingHere: 'something else', }, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -994,7 +991,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note] does validate', () => { - const payload: AddPrepackagedRulesSchema = { + const payload: PrebuiltRuleToInstall = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1009,7 +1006,7 @@ describe('add prepackaged rules schema', () => { version: 1, }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1018,7 +1015,7 @@ describe('add prepackaged rules schema', () => { describe('exception_list', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, version, and exceptions_list] does validate', () => { - const payload: AddPrepackagedRulesSchema = { + const payload: PrebuiltRuleToInstall = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1035,14 +1032,14 @@ describe('add prepackaged rules schema', () => { exceptions_list: getListArrayMock(), }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, version, and empty exceptions_list] does validate', () => { - const payload: AddPrepackagedRulesSchema = { + const payload: PrebuiltRuleToInstall = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1059,7 +1056,7 @@ describe('add prepackaged rules schema', () => { exceptions_list: [], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1083,7 +1080,7 @@ describe('add prepackaged rules schema', () => { exceptions_list: [{ id: 'uuid_here', namespace_type: 'not a namespace type' }], }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1095,7 +1092,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, version, and non-existent exceptions_list] does validate with empty exceptions_list', () => { - const payload: AddPrepackagedRulesSchema = { + const payload: PrebuiltRuleToInstall = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1111,7 +1108,7 @@ describe('add prepackaged rules schema', () => { note: '# some markdown', }; - const decoded = addPrepackagedRulesSchema.decode(payload); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1120,8 +1117,8 @@ describe('add prepackaged rules schema', () => { describe('threat_mapping', () => { test('You can set a threat query, index, mapping, filters on a pre-packaged rule', () => { - const payload = getAddPrepackagedThreatMatchRulesSchemaMock(); - const decoded = addPrepackagedRulesSchema.decode(payload); + const payload = getPrebuiltThreatMatchRuleMock(); + const decoded = PrebuiltRuleToInstall.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule.ts similarity index 53% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule.ts index a836fc2ba2c10..e46615ee992b7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule.ts @@ -6,23 +6,33 @@ */ import * as t from 'io-ts'; - -import { version } from '@kbn/securitysolution-io-ts-types'; - -import { rule_id, RelatedIntegrationArray, RequiredFieldArray, SetupGuide } from '../common'; -import { baseCreateParams, createTypeSpecific } from './rule_schemas'; +import { + RelatedIntegrationArray, + RequiredFieldArray, + SetupGuide, + RuleSignatureId, + RuleVersion, + BaseCreateProps, + TypeSpecificCreateProps, +} from '../../rule_schema'; /** * Big differences between this schema and the createRulesSchema * - rule_id is required here * - version is a required field that must exist */ -export const addPrepackagedRulesSchema = t.intersection([ - baseCreateParams, - createTypeSpecific, - // version is required in addPrepackagedRulesSchema, so this supercedes the defaultable +export type PrebuiltRuleToInstall = t.TypeOf<typeof PrebuiltRuleToInstall>; +export const PrebuiltRuleToInstall = t.intersection([ + BaseCreateProps, + TypeSpecificCreateProps, + // version is required in PrebuiltRuleToInstall, so this supercedes the defaultable // version in baseParams - t.exact(t.type({ rule_id, version })), + t.exact( + t.type({ + rule_id: RuleSignatureId, + version: RuleVersion, + }) + ), t.exact( t.partial({ related_integrations: RelatedIntegrationArray, @@ -31,4 +41,3 @@ export const addPrepackagedRulesSchema = t.intersection([ }) ), ]); -export type AddPrepackagedRulesSchema = t.TypeOf<typeof addPrepackagedRulesSchema>; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule_validate_type_dependents.test.ts similarity index 74% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule_validate_type_dependents.test.ts index f9fd09d0a956b..33021d0fc9a1e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule_validate_type_dependents.test.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { AddPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; -import { addPrepackagedRuleValidateTypeDependents } from './add_prepackaged_rules_type_dependents'; -import { getAddPrepackagedRulesSchemaMock } from './add_prepackaged_rules_schema.mock'; +import type { PrebuiltRuleToInstall } from './prebuilt_rule'; +import { addPrepackagedRuleValidateTypeDependents } from './prebuilt_rule_validate_type_dependents'; +import { getPrebuiltRuleMock } from './prebuilt_rule.mock'; -describe('add_prepackaged_rules_type_dependents', () => { +describe('addPrepackagedRuleValidateTypeDependents', () => { test('You cannot omit timeline_title when timeline_id is present', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const schema: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), timeline_id: '123', }; delete schema.timeline_title; @@ -21,8 +21,8 @@ describe('add_prepackaged_rules_type_dependents', () => { }); test('You cannot have empty string for timeline_title when timeline_id is present', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const schema: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), timeline_id: '123', timeline_title: '', }; @@ -31,8 +31,8 @@ describe('add_prepackaged_rules_type_dependents', () => { }); test('You cannot have timeline_title with an empty timeline_id', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const schema: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), timeline_id: '', timeline_title: 'some-title', }; @@ -41,8 +41,8 @@ describe('add_prepackaged_rules_type_dependents', () => { }); test('You cannot have timeline_title without timeline_id', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), + const schema: PrebuiltRuleToInstall = { + ...getPrebuiltRuleMock(), timeline_title: 'some-title', }; delete schema.timeline_id; @@ -52,33 +52,33 @@ describe('add_prepackaged_rules_type_dependents', () => { test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => { const schema = { - ...getAddPrepackagedRulesSchemaMock(), + ...getPrebuiltRuleMock(), type: 'threshold', threshold: { field: '', value: -1, }, }; - const errors = addPrepackagedRuleValidateTypeDependents(schema as AddPrepackagedRulesSchema); + const errors = addPrepackagedRuleValidateTypeDependents(schema as PrebuiltRuleToInstall); expect(errors).toEqual(['"threshold.value" has to be bigger than 0']); }); test('threshold.field should contain 3 items or less', () => { const schema = { - ...getAddPrepackagedRulesSchemaMock(), + ...getPrebuiltRuleMock(), type: 'threshold', threshold: { field: ['field-1', 'field-2', 'field-3', 'field-4'], value: 1, }, }; - const errors = addPrepackagedRuleValidateTypeDependents(schema as AddPrepackagedRulesSchema); + const errors = addPrepackagedRuleValidateTypeDependents(schema as PrebuiltRuleToInstall); expect(errors).toEqual(['Number of fields must be 3 or less']); }); test('threshold.cardinality[0].field should not be in threshold.field', () => { const schema = { - ...getAddPrepackagedRulesSchemaMock(), + ...getPrebuiltRuleMock(), type: 'threshold', threshold: { field: ['field-1', 'field-2', 'field-3'], @@ -91,7 +91,7 @@ describe('add_prepackaged_rules_type_dependents', () => { ], }, }; - const errors = addPrepackagedRuleValidateTypeDependents(schema as AddPrepackagedRulesSchema); + const errors = addPrepackagedRuleValidateTypeDependents(schema as PrebuiltRuleToInstall); expect(errors).toEqual(['Cardinality of a field that is being aggregated on is always 1']); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule_validate_type_dependents.ts similarity index 79% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts rename to x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule_validate_type_dependents.ts index c2595163b83be..3d9264b3f0556 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/model/prebuilt_rule_validate_type_dependents.ts @@ -5,9 +5,13 @@ * 2.0. */ -import type { AddPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; +import type { PrebuiltRuleToInstall } from './prebuilt_rule'; -export const validateTimelineId = (rule: AddPrepackagedRulesSchema): string[] => { +export const addPrepackagedRuleValidateTypeDependents = (rule: PrebuiltRuleToInstall): string[] => { + return [...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule)]; +}; + +const validateTimelineId = (rule: PrebuiltRuleToInstall): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { return ['when "timeline_id" exists, "timeline_title" must also exist']; @@ -20,7 +24,7 @@ export const validateTimelineId = (rule: AddPrepackagedRulesSchema): string[] => return []; }; -export const validateTimelineTitle = (rule: AddPrepackagedRulesSchema): string[] => { +const validateTimelineTitle = (rule: PrebuiltRuleToInstall): string[] => { if (rule.timeline_title != null) { if (rule.timeline_id == null) { return ['when "timeline_title" exists, "timeline_id" must also exist']; @@ -33,7 +37,7 @@ export const validateTimelineTitle = (rule: AddPrepackagedRulesSchema): string[] return []; }; -export const validateThreshold = (rule: AddPrepackagedRulesSchema): string[] => { +const validateThreshold = (rule: PrebuiltRuleToInstall): string[] => { const errors: string[] = []; if (rule.type === 'threshold') { if (!rule.threshold) { @@ -55,9 +59,3 @@ export const validateThreshold = (rule: AddPrepackagedRulesSchema): string[] => } return errors; }; - -export const addPrepackagedRuleValidateTypeDependents = ( - rule: AddPrepackagedRulesSchema -): string[] => { - return [...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule)]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rule_exception_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/create_rule_exceptions/request_schema.test.ts similarity index 62% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rule_exception_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/create_rule_exceptions/request_schema.test.ts index b76d8cd7cfe4a..93b4e9f128a7b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rule_exception_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/create_rule_exceptions/request_schema.test.ts @@ -5,19 +5,47 @@ * 2.0. */ -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { createRuleExceptionsSchema } from './create_rule_exception_schema'; -import type { CreateRuleExceptionSchema } from './create_rule_exception_schema'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { getCreateExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; +import { + CreateRuleExceptionsRequestBody, + CreateRuleExceptionsRequestParams, +} from './request_schema'; + +describe('CreateRuleExceptionsRequestParams', () => { + test('empty objects do not validate', () => { + const payload: Partial<CreateRuleExceptionsRequestParams> = {}; + + const decoded = CreateRuleExceptionsRequestParams.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['Invalid value "undefined" supplied to "id"']); + expect(message.schema).toEqual({}); + }); + + test('validates string for id', () => { + const payload: Partial<CreateRuleExceptionsRequestParams> = { + id: '4656dc92-5832-11ea-8e2d-0242ac130003', + }; + + const decoded = CreateRuleExceptionsRequestParams.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual({ + id: '4656dc92-5832-11ea-8e2d-0242ac130003', + }); + }); +}); -describe('createRuleExceptionsSchema', () => { +describe('CreateRuleExceptionsRequestBody', () => { test('empty objects do not validate', () => { - const payload: CreateRuleExceptionSchema = {} as CreateRuleExceptionSchema; + const payload: CreateRuleExceptionsRequestBody = {} as CreateRuleExceptionsRequestBody; - const decoded = createRuleExceptionsSchema.decode(payload); + const decoded = CreateRuleExceptionsRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -27,7 +55,7 @@ describe('createRuleExceptionsSchema', () => { }); test('items without list_id validate', () => { - const payload: CreateRuleExceptionSchema = { + const payload: CreateRuleExceptionsRequestBody = { items: [ { description: 'Exception item for rule default exception list', @@ -45,12 +73,12 @@ describe('createRuleExceptionsSchema', () => { ], }; - const decoded = createRuleExceptionsSchema.decode(payload); + const decoded = CreateRuleExceptionsRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as CreateRuleExceptionSchema).items[0]).toEqual( + expect((message.schema as CreateRuleExceptionsRequestBody).items[0]).toEqual( expect.objectContaining({ comments: [], description: 'Exception item for rule default exception list', @@ -73,9 +101,9 @@ describe('createRuleExceptionsSchema', () => { test('items with list_id do not validate', () => { const payload = { items: [getCreateExceptionListItemSchemaMock()], - } as unknown as CreateRuleExceptionSchema; + } as unknown as CreateRuleExceptionsRequestBody; - const decoded = createRuleExceptionsSchema.decode(payload); + const decoded = CreateRuleExceptionsRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -85,7 +113,7 @@ describe('createRuleExceptionsSchema', () => { }); test('made up parameters do not validate', () => { - const payload: Partial<CreateRuleExceptionSchema> & { madeUp: string } = { + const payload: Partial<CreateRuleExceptionsRequestBody> & { madeUp: string } = { items: [ { description: 'Exception item for rule default exception list', @@ -104,7 +132,7 @@ describe('createRuleExceptionsSchema', () => { madeUp: 'invalid value', }; - const decoded = createRuleExceptionsSchema.decode(payload); + const decoded = CreateRuleExceptionsRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/create_rule_exceptions/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/create_rule_exceptions/request_schema.ts new file mode 100644 index 0000000000000..e5777f9725383 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/create_rule_exceptions/request_schema.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; + +import type { CreateRuleExceptionListItemSchemaDecoded } from '@kbn/securitysolution-io-ts-list-types'; +import { createRuleExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import type { RequiredKeepUndefined } from '@kbn/osquery-plugin/common/types'; + +import { RuleObjectId } from '../../../rule_schema'; + +/** + * URL path parameters of the API route. + */ +export type CreateRuleExceptionsRequestParams = t.TypeOf<typeof CreateRuleExceptionsRequestParams>; +export const CreateRuleExceptionsRequestParams = t.exact( + t.type({ + id: RuleObjectId, + }) +); + +export type CreateRuleExceptionsRequestParamsDecoded = CreateRuleExceptionsRequestParams; + +/** + * Request body parameters of the API route. + */ +export type CreateRuleExceptionsRequestBody = t.TypeOf<typeof CreateRuleExceptionsRequestBody>; +export const CreateRuleExceptionsRequestBody = t.exact( + t.type({ + items: t.array(createRuleExceptionListItemSchema), + }) +); + +/** + * This type is used after a decode since some things are defaults after a decode. + */ +export type CreateRuleExceptionsRequestBodyDecoded = Omit< + RequiredKeepUndefined<CreateRuleExceptionsRequestBody>, + 'items' +> & { + items: CreateRuleExceptionListItemSchemaDecoded[]; +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_exception_list_references_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/find_exception_references/request_schema.test.ts similarity index 92% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_exception_list_references_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/find_exception_references/request_schema.test.ts index 60b7fd9141b29..644924e362d08 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_exception_list_references_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/find_exception_references/request_schema.test.ts @@ -6,10 +6,10 @@ */ import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils'; -import { findExceptionReferencesOnRuleSchema } from './find_exception_list_references_schema'; -import type { FindExceptionReferencesOnRuleSchema } from './find_exception_list_references_schema'; +import { findExceptionReferencesOnRuleSchema } from './request_schema'; +import type { FindExceptionReferencesOnRuleSchema } from './request_schema'; -describe('find_exception_list_references_schema', () => { +describe('Find exception list references schema', () => { test('validates all fields', () => { const payload: FindExceptionReferencesOnRuleSchema = { ids: 'abc,def', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_exception_list_references_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/find_exception_references/request_schema.ts similarity index 100% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_exception_list_references_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/find_exception_references/request_schema.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_exception_list_references_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/find_exception_references/response_schema.test.ts similarity index 97% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_exception_list_references_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/find_exception_references/response_schema.test.ts index d8ab3d64fe783..c899ad46c3628 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_exception_list_references_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/find_exception_references/response_schema.test.ts @@ -6,17 +6,17 @@ */ import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils'; +import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; import { exceptionListRuleReferencesSchema, rulesReferencedByExceptionListsSchema, -} from './find_exception_list_references_schema'; +} from './response_schema'; import type { ExceptionListRuleReferencesSchema, RulesReferencedByExceptionListsSchema, -} from './find_exception_list_references_schema'; -import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; +} from './response_schema'; -describe('find_exception_list_references_schema', () => { +describe('Find exception list references response schema', () => { describe('exceptionListRuleReferencesSchema', () => { test('validates all fields', () => { const payload: ExceptionListRuleReferencesSchema = { diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_exception_list_references_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/find_exception_references/response_schema.ts similarity index 89% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_exception_list_references_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/find_exception_references/response_schema.ts index 2bdcd0ba4cc2b..32589e2d165ea 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_exception_list_references_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/find_exception_references/response_schema.ts @@ -6,16 +6,14 @@ */ import * as t from 'io-ts'; - import { exceptionListSchema, listArray, list_id } from '@kbn/securitysolution-io-ts-list-types'; - -import { rule_id, id, name } from '../common/schemas'; +import { RuleName, RuleObjectId, RuleSignatureId } from '../../../rule_schema'; export const ruleReferenceRuleInfoSchema = t.exact( t.type({ - name, - id, - rule_id, + name: RuleName, + id: RuleObjectId, + rule_id: RuleSignatureId, exception_lists: listArray, }) ); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/urls.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/urls.ts new file mode 100644 index 0000000000000..1cc8e2ded7483 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/api/urls.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 { + DETECTION_ENGINE_RULES_URL as PUBLIC_RULES_URL, + INTERNAL_DETECTION_ENGINE_URL as INTERNAL_URL, +} from '../../../constants'; + +const INTERNAL_RULES_URL = `${INTERNAL_URL}/rules`; + +export const CREATE_RULE_EXCEPTIONS_URL = `${PUBLIC_RULES_URL}/{id}/exceptions`; +export const DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL = + `${INTERNAL_RULES_URL}/exceptions/_find_references` as const; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/index.ts new file mode 100644 index 0000000000000..d4b2fa83a4c58 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_exceptions/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 * from './api/create_rule_exceptions/request_schema'; +export * from './api/find_exception_references/request_schema'; +export * from './api/find_exception_references/response_schema'; +export * from './api/urls'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.mock.ts similarity index 75% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.mock.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.mock.ts index 1768acc8dfbfa..548d8b571660e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.mock.ts @@ -5,16 +5,16 @@ * 2.0. */ -import { BulkAction, BulkActionEditType } from './perform_bulk_action_schema'; -import type { PerformBulkActionSchema } from './perform_bulk_action_schema'; +import { BulkAction, BulkActionEditType } from './request_schema'; +import type { PerformBulkActionRequestBody } from './request_schema'; -export const getPerformBulkActionSchemaMock = (): PerformBulkActionSchema => ({ +export const getPerformBulkActionSchemaMock = (): PerformBulkActionRequestBody => ({ query: '', ids: undefined, action: BulkAction.disable, }); -export const getPerformBulkActionEditSchemaMock = (): PerformBulkActionSchema => ({ +export const getPerformBulkActionEditSchemaMock = (): PerformBulkActionRequestBody => ({ query: '', ids: undefined, action: BulkAction.edit, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts similarity index 94% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts index e8b276fa44ace..63de1d45ec3cb 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.test.ts @@ -5,26 +5,21 @@ * 2.0. */ -import type { PerformBulkActionSchema } from './perform_bulk_action_schema'; -import { - performBulkActionSchema, - BulkAction, - BulkActionEditType, -} from './perform_bulk_action_schema'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { left } from 'fp-ts/lib/Either'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; +import { PerformBulkActionRequestBody, BulkAction, BulkActionEditType } from './request_schema'; const retrieveValidationMessage = (payload: unknown) => { - const decoded = performBulkActionSchema.decode(payload); + const decoded = PerformBulkActionRequestBody.decode(payload); const checked = exactCheck(payload, decoded); return foldLeftRight(checked); }; -describe('perform_bulk_action_schema', () => { +describe('Perform bulk action request schema', () => { describe('cases common to every bulk action', () => { // missing query means it will request for all rules test('valid request: missing query', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: undefined, action: BulkAction.enable, }; @@ -35,7 +30,7 @@ describe('perform_bulk_action_schema', () => { }); test('invalid request: missing action', () => { - const payload: Omit<PerformBulkActionSchema, 'action'> = { + const payload: Omit<PerformBulkActionRequestBody, 'action'> = { query: 'name: test', }; const message = retrieveValidationMessage(payload); @@ -48,7 +43,7 @@ describe('perform_bulk_action_schema', () => { }); test('invalid request: unknown action', () => { - const payload: Omit<PerformBulkActionSchema, 'action'> & { action: 'unknown' } = { + const payload: Omit<PerformBulkActionRequestBody, 'action'> & { action: 'unknown' } = { query: 'name: test', action: 'unknown', }; @@ -87,7 +82,7 @@ describe('perform_bulk_action_schema', () => { describe('bulk enable', () => { test('valid request', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.enable, }; @@ -99,7 +94,7 @@ describe('perform_bulk_action_schema', () => { describe('bulk disable', () => { test('valid request', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.disable, }; @@ -111,7 +106,7 @@ describe('perform_bulk_action_schema', () => { describe('bulk export', () => { test('valid request', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.export, }; @@ -123,7 +118,7 @@ describe('perform_bulk_action_schema', () => { describe('bulk delete', () => { test('valid request', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.delete, }; @@ -135,7 +130,7 @@ describe('perform_bulk_action_schema', () => { describe('bulk duplicate', () => { test('valid request', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.duplicate, }; @@ -271,7 +266,7 @@ describe('perform_bulk_action_schema', () => { }); test('valid request: set_index_patterns edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [{ type: BulkActionEditType.set_index_patterns, value: ['logs-*'] }], @@ -284,7 +279,7 @@ describe('perform_bulk_action_schema', () => { }); test('valid request: add_index_patterns edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [{ type: BulkActionEditType.add_index_patterns, value: ['logs-*'] }], @@ -297,7 +292,7 @@ describe('perform_bulk_action_schema', () => { }); test('valid request: delete_index_patterns edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [ @@ -356,7 +351,7 @@ describe('perform_bulk_action_schema', () => { }); test('valid request: set_timeline edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [ @@ -408,7 +403,7 @@ describe('perform_bulk_action_schema', () => { }, }, ], - } as PerformBulkActionSchema; + } as PerformBulkActionRequestBody; const message = retrieveValidationMessage(payload); @@ -434,7 +429,7 @@ describe('perform_bulk_action_schema', () => { }, }, ], - } as PerformBulkActionSchema; + } as PerformBulkActionRequestBody; const message = retrieveValidationMessage(payload); @@ -460,7 +455,7 @@ describe('perform_bulk_action_schema', () => { }, }, ], - } as PerformBulkActionSchema; + } as PerformBulkActionRequestBody; const message = retrieveValidationMessage(payload); @@ -475,7 +470,7 @@ describe('perform_bulk_action_schema', () => { }); test('valid request: set_schedule edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [ @@ -487,7 +482,7 @@ describe('perform_bulk_action_schema', () => { }, }, ], - } as PerformBulkActionSchema; + } as PerformBulkActionRequestBody; const message = retrieveValidationMessage(payload); @@ -590,7 +585,7 @@ describe('perform_bulk_action_schema', () => { }); test('valid request: add_rule_actions edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [ @@ -621,7 +616,7 @@ describe('perform_bulk_action_schema', () => { }); test('valid request: set_rule_actions edit action', () => { - const payload: PerformBulkActionSchema = { + const payload: PerformBulkActionRequestBody = { query: 'name: test', action: BulkAction.edit, [BulkAction.edit]: [ diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts similarity index 59% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts index 0140e5f8d9262..6e5248075b7dc 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/perform_bulk_action_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema.ts @@ -6,15 +6,21 @@ */ import * as t from 'io-ts'; -import { NonEmptyArray, TimeDuration, enumeration } from '@kbn/securitysolution-io-ts-types'; +import { NonEmptyArray, TimeDuration } from '@kbn/securitysolution-io-ts-types'; import { - action_group as actionGroup, - action_params as actionParams, - action_id as actionId, + RuleActionGroup, + RuleActionId, + RuleActionParams, } from '@kbn/securitysolution-io-ts-alerting-types'; -import { queryOrUndefined, tags, index, timeline_id, timeline_title } from '../common/schemas'; +import { + IndexPatternArray, + RuleQuery, + RuleTagArray, + TimelineTemplateId, + TimelineTemplateTitle, +} from '../../../../rule_schema'; export enum BulkAction { 'enable' = 'enable', @@ -25,8 +31,6 @@ export enum BulkAction { 'edit' = 'edit', } -export const bulkAction = enumeration('BulkAction', BulkAction); - export enum BulkActionEditType { 'add_tags' = 'add_tags', 'delete_tags' = 'delete_tags', @@ -40,7 +44,8 @@ export enum BulkActionEditType { 'set_schedule' = 'set_schedule', } -export const throttleForBulkActions = t.union([ +export type ThrottleForBulkActions = t.TypeOf<typeof ThrottleForBulkActions>; +export const ThrottleForBulkActions = t.union([ t.literal('rule'), TimeDuration({ allowedDurations: [ @@ -50,88 +55,81 @@ export const throttleForBulkActions = t.union([ ], }), ]); -export type ThrottleForBulkActions = t.TypeOf<typeof throttleForBulkActions>; -const bulkActionEditPayloadTags = t.type({ +type BulkActionEditPayloadTags = t.TypeOf<typeof BulkActionEditPayloadTags>; +const BulkActionEditPayloadTags = t.type({ type: t.union([ t.literal(BulkActionEditType.add_tags), t.literal(BulkActionEditType.delete_tags), t.literal(BulkActionEditType.set_tags), ]), - value: tags, + value: RuleTagArray, }); -export type BulkActionEditPayloadTags = t.TypeOf<typeof bulkActionEditPayloadTags>; - -const bulkActionEditPayloadIndexPatterns = t.intersection([ +type BulkActionEditPayloadIndexPatterns = t.TypeOf<typeof BulkActionEditPayloadIndexPatterns>; +const BulkActionEditPayloadIndexPatterns = t.intersection([ t.type({ type: t.union([ t.literal(BulkActionEditType.add_index_patterns), t.literal(BulkActionEditType.delete_index_patterns), t.literal(BulkActionEditType.set_index_patterns), ]), - value: index, + value: IndexPatternArray, }), t.exact(t.partial({ overwrite_data_views: t.boolean })), ]); -export type BulkActionEditPayloadIndexPatterns = t.TypeOf< - typeof bulkActionEditPayloadIndexPatterns ->; - -const bulkActionEditPayloadTimeline = t.type({ +type BulkActionEditPayloadTimeline = t.TypeOf<typeof BulkActionEditPayloadTimeline>; +const BulkActionEditPayloadTimeline = t.type({ type: t.literal(BulkActionEditType.set_timeline), value: t.type({ - timeline_id, - timeline_title, + timeline_id: TimelineTemplateId, + timeline_title: TimelineTemplateTitle, }), }); -export type BulkActionEditPayloadTimeline = t.TypeOf<typeof bulkActionEditPayloadTimeline>; - /** * per rulesClient.bulkEdit rules actions operation contract (x-pack/plugins/alerting/server/rules_client/rules_client.ts) * normalized rule action object is expected (NormalizedAlertAction) as value for the edit operation */ -const normalizedRuleAction = t.exact( +type NormalizedRuleAction = t.TypeOf<typeof NormalizedRuleAction>; +const NormalizedRuleAction = t.exact( t.type({ - group: actionGroup, - id: actionId, - params: actionParams, + group: RuleActionGroup, + id: RuleActionId, + params: RuleActionParams, }) ); -const bulkActionEditPayloadRuleActions = t.type({ +export type BulkActionEditPayloadRuleActions = t.TypeOf<typeof BulkActionEditPayloadRuleActions>; +export const BulkActionEditPayloadRuleActions = t.type({ type: t.union([ t.literal(BulkActionEditType.add_rule_actions), t.literal(BulkActionEditType.set_rule_actions), ]), value: t.type({ - throttle: throttleForBulkActions, - actions: t.array(normalizedRuleAction), + throttle: ThrottleForBulkActions, + actions: t.array(NormalizedRuleAction), }), }); -export type BulkActionEditPayloadRuleActions = t.TypeOf<typeof bulkActionEditPayloadRuleActions>; - -const bulkActionEditPayloadSchedule = t.type({ +type BulkActionEditPayloadSchedule = t.TypeOf<typeof BulkActionEditPayloadSchedule>; +const BulkActionEditPayloadSchedule = t.type({ type: t.literal(BulkActionEditType.set_schedule), value: t.type({ interval: TimeDuration({ allowedUnits: ['s', 'm', 'h'] }), lookback: TimeDuration({ allowedUnits: ['s', 'm', 'h'] }), }), }); -export type BulkActionEditPayloadSchedule = t.TypeOf<typeof bulkActionEditPayloadSchedule>; - -export const bulkActionEditPayload = t.union([ - bulkActionEditPayloadTags, - bulkActionEditPayloadIndexPatterns, - bulkActionEditPayloadTimeline, - bulkActionEditPayloadRuleActions, - bulkActionEditPayloadSchedule, -]); -export type BulkActionEditPayload = t.TypeOf<typeof bulkActionEditPayload>; +export type BulkActionEditPayload = t.TypeOf<typeof BulkActionEditPayload>; +export const BulkActionEditPayload = t.union([ + BulkActionEditPayloadTags, + BulkActionEditPayloadIndexPatterns, + BulkActionEditPayloadTimeline, + BulkActionEditPayloadRuleActions, + BulkActionEditPayloadSchedule, +]); /** * actions that modify rules attributes @@ -149,10 +147,14 @@ export type BulkActionEditForRuleParams = | BulkActionEditPayloadTimeline | BulkActionEditPayloadSchedule; -export const performBulkActionSchema = t.intersection([ +/** + * Request body parameters of the API route. + */ +export type PerformBulkActionRequestBody = t.TypeOf<typeof PerformBulkActionRequestBody>; +export const PerformBulkActionRequestBody = t.intersection([ t.exact( t.type({ - query: queryOrUndefined, + query: t.union([RuleQuery, t.undefined]), }) ), t.exact(t.partial({ ids: NonEmptyArray(t.string) })), @@ -171,16 +173,18 @@ export const performBulkActionSchema = t.intersection([ t.exact( t.type({ action: t.literal(BulkAction.edit), - [BulkAction.edit]: NonEmptyArray(bulkActionEditPayload), + [BulkAction.edit]: NonEmptyArray(BulkActionEditPayload), }) ), ]), ]); -export const performBulkActionQuerySchema = t.exact( +/** + * Query string parameters of the API route. + */ +export type PerformBulkActionRequestQuery = t.TypeOf<typeof PerformBulkActionRequestQuery>; +export const PerformBulkActionRequestQuery = t.exact( t.partial({ dry_run: t.union([t.literal('true'), t.literal('false')]), }) ); - -export type PerformBulkActionSchema = t.TypeOf<typeof performBulkActionSchema>; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_create_rules/request_schema.test.ts similarity index 77% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_bulk_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_create_rules/request_schema.test.ts index 5155b593583f9..d2c297d401edb 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_create_rules/request_schema.test.ts @@ -5,19 +5,18 @@ * 2.0. */ -import type { CreateRulesBulkSchema } from './create_rules_bulk_schema'; -import { createRulesBulkSchema } from './create_rules_bulk_schema'; +import { BulkCreateRulesRequestBody } from './request_schema'; import { exactCheck, foldLeftRight, formatErrors } from '@kbn/securitysolution-io-ts-utils'; -import { getCreateRulesSchemaMock } from './rule_schemas.mock'; +import { getCreateRulesSchemaMock } from '../../../../../rule_schema/mocks'; // only the basics of testing are here. // see: rule_schemas.test.ts for the bulk of the validation tests // this just wraps createRulesSchema in an array -describe('create_rules_bulk_schema', () => { +describe('Bulk create rules request schema', () => { test('can take an empty array and validate it', () => { - const payload: CreateRulesBulkSchema = []; + const payload: BulkCreateRulesRequestBody = []; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(output.errors).toEqual([]); @@ -27,7 +26,7 @@ describe('create_rules_bulk_schema', () => { test('made up values do not validate for a single element', () => { const payload: Array<{ madeUp: string }> = [{ madeUp: 'hi' }]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toContain( @@ -44,9 +43,9 @@ describe('create_rules_bulk_schema', () => { }); test('single array element does validate', () => { - const payload: CreateRulesBulkSchema = [getCreateRulesSchemaMock()]; + const payload: BulkCreateRulesRequestBody = [getCreateRulesSchemaMock()]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -54,9 +53,12 @@ describe('create_rules_bulk_schema', () => { }); test('two array elements do validate', () => { - const payload: CreateRulesBulkSchema = [getCreateRulesSchemaMock(), getCreateRulesSchemaMock()]; + const payload: BulkCreateRulesRequestBody = [ + getCreateRulesSchemaMock(), + getCreateRulesSchemaMock(), + ]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -67,9 +69,9 @@ describe('create_rules_bulk_schema', () => { const singleItem = getCreateRulesSchemaMock(); // @ts-expect-error delete singleItem.risk_score; - const payload: CreateRulesBulkSchema = [singleItem]; + const payload: BulkCreateRulesRequestBody = [singleItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -83,9 +85,9 @@ describe('create_rules_bulk_schema', () => { const secondItem = getCreateRulesSchemaMock(); // @ts-expect-error delete secondItem.risk_score; - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -99,9 +101,9 @@ describe('create_rules_bulk_schema', () => { const secondItem = getCreateRulesSchemaMock(); // @ts-expect-error delete singleItem.risk_score; - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -117,9 +119,9 @@ describe('create_rules_bulk_schema', () => { delete singleItem.risk_score; // @ts-expect-error delete secondItem.risk_score; - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -134,9 +136,9 @@ describe('create_rules_bulk_schema', () => { madeUpValue: 'something', }; const secondItem = getCreateRulesSchemaMock(); - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']); @@ -149,9 +151,9 @@ describe('create_rules_bulk_schema', () => { ...getCreateRulesSchemaMock(), madeUpValue: 'something', }; - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']); @@ -167,9 +169,9 @@ describe('create_rules_bulk_schema', () => { ...getCreateRulesSchemaMock(), madeUpValue: 'something', }; - const payload: CreateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkCreateRulesRequestBody = [singleItem, secondItem]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue,madeUpValue"']); @@ -180,7 +182,7 @@ describe('create_rules_bulk_schema', () => { const badSeverity = { ...getCreateRulesSchemaMock(), severity: 'madeup' }; const payload = [badSeverity]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['Invalid value "madeup" supplied to "severity"']); @@ -188,11 +190,11 @@ describe('create_rules_bulk_schema', () => { }); test('You can set "note" to a string', () => { - const payload: CreateRulesBulkSchema = [ + const payload: BulkCreateRulesRequestBody = [ { ...getCreateRulesSchemaMock(), note: '# test markdown' }, ]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -200,9 +202,9 @@ describe('create_rules_bulk_schema', () => { }); test('You can set "note" to an empty string', () => { - const payload: CreateRulesBulkSchema = [{ ...getCreateRulesSchemaMock(), note: '' }]; + const payload: BulkCreateRulesRequestBody = [{ ...getCreateRulesSchemaMock(), note: '' }]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -219,7 +221,7 @@ describe('create_rules_bulk_schema', () => { }, ]; - const decoded = createRulesBulkSchema.decode(payload); + const decoded = BulkCreateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_create_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_create_rules/request_schema.ts new file mode 100644 index 0000000000000..846da78b4cbba --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_create_rules/request_schema.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 * as t from 'io-ts'; +import { RuleCreateProps } from '../../../../../rule_schema'; + +/** + * Request body parameters of the API route. + */ +export type BulkCreateRulesRequestBody = t.TypeOf<typeof BulkCreateRulesRequestBody>; +export const BulkCreateRulesRequestBody = t.array(RuleCreateProps); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_delete_rules/request_schema.test.ts similarity index 73% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_bulk_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_delete_rules/request_schema.test.ts index dfbb168f24520..6a709db317109 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_delete_rules/request_schema.test.ts @@ -5,18 +5,17 @@ * 2.0. */ -import type { QueryRulesBulkSchema } from './query_rules_bulk_schema'; -import { queryRulesBulkSchema } from './query_rules_bulk_schema'; import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils'; +import { BulkDeleteRulesRequestBody } from './request_schema'; // only the basics of testing are here. // see: query_rules_schema.test.ts for the bulk of the validation tests // this just wraps queryRulesSchema in an array -describe('query_rules_bulk_schema', () => { +describe('Bulk delete rules request schema', () => { test('can take an empty array and validate it', () => { - const payload: QueryRulesBulkSchema = []; + const payload: BulkDeleteRulesRequestBody = []; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -24,13 +23,13 @@ describe('query_rules_bulk_schema', () => { }); test('non uuid being supplied to id does not validate', () => { - const payload: QueryRulesBulkSchema = [ + const payload: BulkDeleteRulesRequestBody = [ { id: '1', }, ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['Invalid value "1" supplied to "id"']); @@ -38,14 +37,14 @@ describe('query_rules_bulk_schema', () => { }); test('both rule_id and id being supplied do validate', () => { - const payload: QueryRulesBulkSchema = [ + const payload: BulkDeleteRulesRequestBody = [ { rule_id: '1', id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f', }, ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -53,12 +52,12 @@ describe('query_rules_bulk_schema', () => { }); test('only id validates with two elements', () => { - const payload: QueryRulesBulkSchema = [ + const payload: BulkDeleteRulesRequestBody = [ { id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }, { id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }, ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -66,9 +65,11 @@ describe('query_rules_bulk_schema', () => { }); test('only rule_id validates', () => { - const payload: QueryRulesBulkSchema = [{ rule_id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }]; + const payload: BulkDeleteRulesRequestBody = [ + { rule_id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }, + ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -76,12 +77,12 @@ describe('query_rules_bulk_schema', () => { }); test('only rule_id validates with two elements', () => { - const payload: QueryRulesBulkSchema = [ + const payload: BulkDeleteRulesRequestBody = [ { rule_id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }, { rule_id: '2' }, ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -89,12 +90,12 @@ describe('query_rules_bulk_schema', () => { }); test('both id and rule_id validates with two separate elements', () => { - const payload: QueryRulesBulkSchema = [ + const payload: BulkDeleteRulesRequestBody = [ { id: 'c1e1b359-7ac1-4e96-bc81-c683c092436f' }, { rule_id: '2' }, ]; - const decoded = queryRulesBulkSchema.decode(payload); + const decoded = BulkDeleteRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_delete_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_delete_rules/request_schema.ts new file mode 100644 index 0000000000000..e290b57b4c404 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_delete_rules/request_schema.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 * as t from 'io-ts'; +import { QueryRuleByIds } from '../../crud/read_rule/query_rule_by_ids'; + +/** + * Request body parameters of the API route. + */ +export type BulkDeleteRulesRequestBody = t.TypeOf<typeof BulkDeleteRulesRequestBody>; +export const BulkDeleteRulesRequestBody = t.array(QueryRuleByIds); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_patch_rules/request_schema.test.ts similarity index 72% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_patch_rules/request_schema.test.ts index 1b9b1f92faf2f..3d466d7d23894 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_patch_rules/request_schema.test.ts @@ -5,19 +5,18 @@ * 2.0. */ -import type { PatchRulesBulkSchema } from './patch_rules_bulk_schema'; -import { patchRulesBulkSchema } from './patch_rules_bulk_schema'; import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils'; -import type { PatchRulesSchema } from './patch_rules_schema'; +import type { PatchRuleRequestBody } from '../../crud/patch_rule/request_schema'; +import { BulkPatchRulesRequestBody } from './request_schema'; // only the basics of testing are here. // see: patch_rules_schema.test.ts for the bulk of the validation tests // this just wraps patchRulesSchema in an array -describe('patch_rules_bulk_schema', () => { +describe('Bulk patch rules request schema', () => { test('can take an empty array and validate it', () => { - const payload: PatchRulesBulkSchema = []; + const payload: BulkPatchRulesRequestBody = []; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(output.errors).toEqual([]); @@ -25,9 +24,9 @@ describe('patch_rules_bulk_schema', () => { }); test('single array of [id] does validate', () => { - const payload: PatchRulesBulkSchema = [{ id: '4125761e-51da-4de9-a0c8-42824f532ddb' }]; + const payload: BulkPatchRulesRequestBody = [{ id: '4125761e-51da-4de9-a0c8-42824f532ddb' }]; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -35,12 +34,12 @@ describe('patch_rules_bulk_schema', () => { }); test('two arrays of [id] validate', () => { - const payload: PatchRulesBulkSchema = [ + const payload: BulkPatchRulesRequestBody = [ { id: '4125761e-51da-4de9-a0c8-42824f532ddb' }, { id: '192f403d-b285-4251-9e8b-785fcfcf22e8' }, ]; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -48,12 +47,12 @@ describe('patch_rules_bulk_schema', () => { }); test('can set "note" to be a string', () => { - const payload: PatchRulesBulkSchema = [ + const payload: BulkPatchRulesRequestBody = [ { id: '4125761e-51da-4de9-a0c8-42824f532ddb' }, { note: 'hi' }, ]; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -61,12 +60,12 @@ describe('patch_rules_bulk_schema', () => { }); test('can set "note" to be an empty string', () => { - const payload: PatchRulesBulkSchema = [ + const payload: BulkPatchRulesRequestBody = [ { id: '4125761e-51da-4de9-a0c8-42824f532ddb' }, { note: '' }, ]; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -74,12 +73,12 @@ describe('patch_rules_bulk_schema', () => { }); test('cannot set "note" to be anything other than a string', () => { - const payload: Array<Omit<PatchRulesSchema, 'note'> & { note?: object }> = [ + const payload: Array<Omit<PatchRuleRequestBody, 'note'> & { note?: object }> = [ { id: '4125761e-51da-4de9-a0c8-42824f532ddb' }, { note: { someprop: 'some value here' } }, ]; - const decoded = patchRulesBulkSchema.decode(payload); + const decoded = BulkPatchRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_patch_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_patch_rules/request_schema.ts new file mode 100644 index 0000000000000..2dcd0c2a22d4b --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_patch_rules/request_schema.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 * as t from 'io-ts'; +import { PatchRuleRequestBody } from '../../crud/patch_rule/request_schema'; + +/** + * Request body parameters of the API route. + */ +export type BulkPatchRulesRequestBody = t.TypeOf<typeof BulkPatchRulesRequestBody>; +export const BulkPatchRulesRequestBody = t.array(PatchRuleRequestBody); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_update_rules/request_schema.test.ts similarity index 74% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_bulk_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_update_rules/request_schema.test.ts index 416e43ccfa967..f2b3437273d01 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_update_rules/request_schema.test.ts @@ -5,20 +5,19 @@ * 2.0. */ -import type { UpdateRulesBulkSchema } from './update_rules_bulk_schema'; -import { updateRulesBulkSchema } from './update_rules_bulk_schema'; import { exactCheck, formatErrors, foldLeftRight } from '@kbn/securitysolution-io-ts-utils'; -import { getUpdateRulesSchemaMock } from './rule_schemas.mock'; -import type { UpdateRulesSchema } from './rule_schemas'; +import type { RuleUpdateProps } from '../../../../../rule_schema'; +import { getUpdateRulesSchemaMock } from '../../../../../rule_schema/mocks'; +import { BulkUpdateRulesRequestBody } from './request_schema'; // only the basics of testing are here. // see: update_rules_schema.test.ts for the bulk of the validation tests // this just wraps updateRulesSchema in an array -describe('update_rules_bulk_schema', () => { +describe('Bulk update rules request schema', () => { test('can take an empty array and validate it', () => { - const payload: UpdateRulesBulkSchema = []; + const payload: BulkUpdateRulesRequestBody = []; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(output.errors).toEqual([]); @@ -28,7 +27,7 @@ describe('update_rules_bulk_schema', () => { test('made up values do not validate for a single element', () => { const payload: Array<{ madeUp: string }> = [{ madeUp: 'hi' }]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toContain( @@ -45,9 +44,9 @@ describe('update_rules_bulk_schema', () => { }); test('single array element does validate', () => { - const payload: UpdateRulesBulkSchema = [getUpdateRulesSchemaMock()]; + const payload: BulkUpdateRulesRequestBody = [getUpdateRulesSchemaMock()]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -55,9 +54,12 @@ describe('update_rules_bulk_schema', () => { }); test('two array elements do validate', () => { - const payload: UpdateRulesBulkSchema = [getUpdateRulesSchemaMock(), getUpdateRulesSchemaMock()]; + const payload: BulkUpdateRulesRequestBody = [ + getUpdateRulesSchemaMock(), + getUpdateRulesSchemaMock(), + ]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -68,9 +70,9 @@ describe('update_rules_bulk_schema', () => { const singleItem = getUpdateRulesSchemaMock(); // @ts-expect-error delete singleItem.risk_score; - const payload: UpdateRulesBulkSchema = [singleItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -84,9 +86,9 @@ describe('update_rules_bulk_schema', () => { const secondItem = getUpdateRulesSchemaMock(); // @ts-expect-error delete secondItem.risk_score; - const payload: UpdateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -100,9 +102,9 @@ describe('update_rules_bulk_schema', () => { const secondItem = getUpdateRulesSchemaMock(); // @ts-expect-error delete singleItem.risk_score; - const payload: UpdateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -118,9 +120,9 @@ describe('update_rules_bulk_schema', () => { delete singleItem.risk_score; // @ts-expect-error delete secondItem.risk_score; - const payload: UpdateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ @@ -130,14 +132,14 @@ describe('update_rules_bulk_schema', () => { }); test('two array elements where the first is invalid (extra key and value) but the second is valid will not validate', () => { - const singleItem: UpdateRulesSchema & { madeUpValue: string } = { + const singleItem: RuleUpdateProps & { madeUpValue: string } = { ...getUpdateRulesSchemaMock(), madeUpValue: 'something', }; const secondItem = getUpdateRulesSchemaMock(); const payload = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']); @@ -145,14 +147,14 @@ describe('update_rules_bulk_schema', () => { }); test('two array elements where the second is invalid (extra key and value) but the first is valid will not validate', () => { - const singleItem: UpdateRulesSchema = getUpdateRulesSchemaMock(); - const secondItem: UpdateRulesSchema & { madeUpValue: string } = { + const singleItem: RuleUpdateProps = getUpdateRulesSchemaMock(); + const secondItem: RuleUpdateProps & { madeUpValue: string } = { ...getUpdateRulesSchemaMock(), madeUpValue: 'something', }; - const payload: UpdateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue"']); @@ -160,17 +162,17 @@ describe('update_rules_bulk_schema', () => { }); test('two array elements where both are invalid (extra key and value) will not validate', () => { - const singleItem: UpdateRulesSchema & { madeUpValue: string } = { + const singleItem: RuleUpdateProps & { madeUpValue: string } = { ...getUpdateRulesSchemaMock(), madeUpValue: 'something', }; - const secondItem: UpdateRulesSchema & { madeUpValue: string } = { + const secondItem: RuleUpdateProps & { madeUpValue: string } = { ...getUpdateRulesSchemaMock(), madeUpValue: 'something', }; - const payload: UpdateRulesBulkSchema = [singleItem, secondItem]; + const payload: BulkUpdateRulesRequestBody = [singleItem, secondItem]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUpValue,madeUpValue"']); @@ -181,7 +183,7 @@ describe('update_rules_bulk_schema', () => { const badSeverity = { ...getUpdateRulesSchemaMock(), severity: 'madeup' }; const payload = [badSeverity]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual(['Invalid value "madeup" supplied to "severity"']); @@ -189,11 +191,11 @@ describe('update_rules_bulk_schema', () => { }); test('You can set "namespace" to a string', () => { - const payload: UpdateRulesBulkSchema = [ + const payload: BulkUpdateRulesRequestBody = [ { ...getUpdateRulesSchemaMock(), namespace: 'a namespace' }, ]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -201,11 +203,11 @@ describe('update_rules_bulk_schema', () => { }); test('You can set "note" to a string', () => { - const payload: UpdateRulesBulkSchema = [ + const payload: BulkUpdateRulesRequestBody = [ { ...getUpdateRulesSchemaMock(), note: '# test markdown' }, ]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -213,9 +215,9 @@ describe('update_rules_bulk_schema', () => { }); test('You can set "note" to an empty string', () => { - const payload: UpdateRulesBulkSchema = [{ ...getUpdateRulesSchemaMock(), note: '' }]; + const payload: BulkUpdateRulesRequestBody = [{ ...getUpdateRulesSchemaMock(), note: '' }]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([]); @@ -232,7 +234,7 @@ describe('update_rules_bulk_schema', () => { }, ]; - const decoded = updateRulesBulkSchema.decode(payload); + const decoded = BulkUpdateRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const output = foldLeftRight(checked); expect(formatErrors(output.errors)).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_update_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_update_rules/request_schema.ts new file mode 100644 index 0000000000000..5409e7b2bda43 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/bulk_update_rules/request_schema.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 * as t from 'io-ts'; +import { RuleUpdateProps } from '../../../../../rule_schema'; + +/** + * Request body parameters of the API route. + */ +export type BulkUpdateRulesRequestBody = t.TypeOf<typeof BulkUpdateRulesRequestBody>; +export const BulkUpdateRulesRequestBody = t.array(RuleUpdateProps); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/response_schema.test.ts similarity index 70% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/response_schema.test.ts index 69e31522ef40a..044af119f99ec 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/response_schema.test.ts @@ -7,20 +7,19 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - -import type { RulesBulkSchema } from './rules_bulk_schema'; -import { rulesBulkSchema } from './rules_bulk_schema'; -import type { ErrorSchema } from './error_schema'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { getRulesSchemaMock } from './rules_schema.mocks'; -import { getErrorSchemaMock } from './error_schema.mocks'; -import type { FullResponseSchema } from '../request'; +import type { RuleResponse } from '../../../../rule_schema'; +import { getRulesSchemaMock } from '../../../../rule_schema/mocks'; +import type { ErrorSchema } from '../../../../schemas/response/error_schema'; +import { getErrorSchemaMock } from '../../../../schemas/response/error_schema.mocks'; + +import { BulkCrudRulesResponse } from './response_schema'; -describe('prepackaged_rule_schema', () => { +describe('Bulk CRUD rules response schema', () => { test('it should validate a regular message and and error together with a uuid', () => { - const payload: RulesBulkSchema = [getRulesSchemaMock(), getErrorSchemaMock()]; - const decoded = rulesBulkSchema.decode(payload); + const payload: BulkCrudRulesResponse = [getRulesSchemaMock(), getErrorSchemaMock()]; + const decoded = BulkCrudRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -29,8 +28,8 @@ describe('prepackaged_rule_schema', () => { }); test('it should validate a regular message and and error together when the error has a non UUID', () => { - const payload: RulesBulkSchema = [getRulesSchemaMock(), getErrorSchemaMock('fake id')]; - const decoded = rulesBulkSchema.decode(payload); + const payload: BulkCrudRulesResponse = [getRulesSchemaMock(), getErrorSchemaMock('fake id')]; + const decoded = BulkCrudRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -39,8 +38,8 @@ describe('prepackaged_rule_schema', () => { }); test('it should validate an error', () => { - const payload: RulesBulkSchema = [getErrorSchemaMock('fake id')]; - const decoded = rulesBulkSchema.decode(payload); + const payload: BulkCrudRulesResponse = [getErrorSchemaMock('fake id')]; + const decoded = BulkCrudRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -52,8 +51,8 @@ describe('prepackaged_rule_schema', () => { const rule = getRulesSchemaMock(); // @ts-expect-error delete rule.name; - const payload: RulesBulkSchema = [rule]; - const decoded = rulesBulkSchema.decode(payload); + const payload: BulkCrudRulesResponse = [rule]; + const decoded = BulkCrudRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -68,8 +67,8 @@ describe('prepackaged_rule_schema', () => { const error = getErrorSchemaMock('fake id'); // @ts-expect-error delete error.error; - const payload: RulesBulkSchema = [error]; - const decoded = rulesBulkSchema.decode(payload); + const payload: BulkCrudRulesResponse = [error]; + const decoded = BulkCrudRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -80,10 +79,10 @@ describe('prepackaged_rule_schema', () => { }); test('it should NOT validate a type of "query" when it has extra data', () => { - const rule: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const rule: RuleResponse & { invalid_extra_data?: string } = getRulesSchemaMock(); rule.invalid_extra_data = 'invalid_extra_data'; - const payload: RulesBulkSchema = [rule]; - const decoded = rulesBulkSchema.decode(payload); + const payload: BulkCrudRulesResponse = [rule]; + const decoded = BulkCrudRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -92,10 +91,10 @@ describe('prepackaged_rule_schema', () => { }); test('it should NOT validate a type of "query" when it has extra data next to a valid error', () => { - const rule: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const rule: RuleResponse & { invalid_extra_data?: string } = getRulesSchemaMock(); rule.invalid_extra_data = 'invalid_extra_data'; - const payload: RulesBulkSchema = [getErrorSchemaMock(), rule]; - const decoded = rulesBulkSchema.decode(payload); + const payload: BulkCrudRulesResponse = [getErrorSchemaMock(), rule]; + const decoded = BulkCrudRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -107,8 +106,8 @@ describe('prepackaged_rule_schema', () => { type InvalidError = ErrorSchema & { invalid_extra_data?: string }; const error: InvalidError = getErrorSchemaMock(); error.invalid_extra_data = 'invalid'; - const payload: RulesBulkSchema = [error]; - const decoded = rulesBulkSchema.decode(payload); + const payload: BulkCrudRulesResponse = [error]; + const decoded = BulkCrudRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -120,8 +119,8 @@ describe('prepackaged_rule_schema', () => { type InvalidError = ErrorSchema & { invalid_extra_data?: string }; const error: InvalidError = getErrorSchemaMock(); error.invalid_extra_data = 'invalid'; - const payload: RulesBulkSchema = [getRulesSchemaMock(), error]; - const decoded = rulesBulkSchema.decode(payload); + const payload: BulkCrudRulesResponse = [getRulesSchemaMock(), error]; + const decoded = BulkCrudRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/response_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/response_schema.ts new file mode 100644 index 0000000000000..9a0144e2cf758 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/bulk_crud/response_schema.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 * as t from 'io-ts'; + +import { RuleResponse } from '../../../../rule_schema'; +import { errorSchema } from '../../../../schemas/response/error_schema'; + +export type BulkCrudRulesResponse = t.TypeOf<typeof BulkCrudRulesResponse>; +export const BulkCrudRulesResponse = t.array(t.union([RuleResponse, errorSchema])); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/create_rule/request_schema_validation.test.ts similarity index 68% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/create_rule/request_schema_validation.test.ts index 1fecd82cc2708..39ed8b028f054 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/create_rule/request_schema_validation.test.ts @@ -5,78 +5,81 @@ * 2.0. */ -import { getCreateRulesSchemaMock, getCreateThreatMatchRulesSchemaMock } from './rule_schemas.mock'; -import type { CreateRulesSchema } from './rule_schemas'; -import { createRuleValidateTypeDependents } from './create_rules_type_dependents'; +import type { RuleCreateProps } from '../../../../../rule_schema'; +import { + getCreateRulesSchemaMock, + getCreateThreatMatchRulesSchemaMock, +} from '../../../../../rule_schema/mocks'; +import { validateCreateRuleProps } from './request_schema_validation'; -describe('create_rules_type_dependents', () => { +describe('Create rule request schema, additional validation', () => { test('You cannot omit timeline_title when timeline_id is present', () => { - const schema: CreateRulesSchema = { + const schema: RuleCreateProps = { ...getCreateRulesSchemaMock(), timeline_id: '123', }; delete schema.timeline_title; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleProps(schema); expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); }); test('You cannot have empty string for timeline_title when timeline_id is present', () => { - const schema: CreateRulesSchema = { + const schema: RuleCreateProps = { ...getCreateRulesSchemaMock(), timeline_id: '123', timeline_title: '', }; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleProps(schema); expect(errors).toEqual(['"timeline_title" cannot be an empty string']); }); test('You cannot have timeline_title with an empty timeline_id', () => { - const schema: CreateRulesSchema = { + const schema: RuleCreateProps = { ...getCreateRulesSchemaMock(), timeline_id: '', timeline_title: 'some-title', }; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleProps(schema); expect(errors).toEqual(['"timeline_id" cannot be an empty string']); }); test('You cannot have timeline_title without timeline_id', () => { - const schema: CreateRulesSchema = { + const schema: RuleCreateProps = { ...getCreateRulesSchemaMock(), timeline_title: 'some-title', }; delete schema.timeline_id; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleProps(schema); expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); }); test('validates that both "items_per_search" and "concurrent_searches" works when together', () => { - const schema: CreateRulesSchema = { + const schema: RuleCreateProps = { ...getCreateThreatMatchRulesSchemaMock(), concurrent_searches: 10, items_per_search: 10, }; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleProps(schema); expect(errors).toEqual([]); }); test('does NOT validate when only "items_per_search" is present', () => { - const schema: CreateRulesSchema = { + const schema: RuleCreateProps = { ...getCreateThreatMatchRulesSchemaMock(), items_per_search: 10, }; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleProps(schema); expect(errors).toEqual([ 'when "items_per_search" exists, "concurrent_searches" must also exist', ]); }); test('does NOT validate when only "concurrent_searches" is present', () => { - const schema: CreateRulesSchema = { + const schema: RuleCreateProps = { ...getCreateThreatMatchRulesSchemaMock(), concurrent_searches: 10, }; - const errors = createRuleValidateTypeDependents(schema); + const errors = validateCreateRuleProps(schema); expect(errors).toEqual([ 'when "concurrent_searches" exists, "items_per_search" must also exist', ]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/create_rule/request_schema_validation.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/create_rule/request_schema_validation.ts new file mode 100644 index 0000000000000..fd2e4b06b63d6 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/create_rule/request_schema_validation.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 type { RuleCreateProps } from '../../../../../rule_schema'; + +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validateCreateRuleProps = (props: RuleCreateProps): string[] => { + return [ + ...validateTimelineId(props), + ...validateTimelineTitle(props), + ...validateThreatMapping(props), + ...validateThreshold(props), + ]; +}; + +const validateTimelineId = (props: RuleCreateProps): string[] => { + if (props.timeline_id != null) { + if (props.timeline_title == null) { + return ['when "timeline_id" exists, "timeline_title" must also exist']; + } else if (props.timeline_id === '') { + return ['"timeline_id" cannot be an empty string']; + } else { + return []; + } + } + return []; +}; + +const validateTimelineTitle = (props: RuleCreateProps): string[] => { + if (props.timeline_title != null) { + if (props.timeline_id == null) { + return ['when "timeline_title" exists, "timeline_id" must also exist']; + } else if (props.timeline_title === '') { + return ['"timeline_title" cannot be an empty string']; + } else { + return []; + } + } + return []; +}; + +const validateThreatMapping = (props: RuleCreateProps): string[] => { + const errors: string[] = []; + if (props.type === 'threat_match') { + if (props.concurrent_searches != null && props.items_per_search == null) { + errors.push('when "concurrent_searches" exists, "items_per_search" must also exist'); + } + if (props.concurrent_searches == null && props.items_per_search != null) { + errors.push('when "items_per_search" exists, "concurrent_searches" must also exist'); + } + } + return errors; +}; + +const validateThreshold = (props: RuleCreateProps): string[] => { + const errors: string[] = []; + if (props.type === 'threshold') { + if (!props.threshold) { + errors.push('when "type" is "threshold", "threshold" is required'); + } else { + if ( + props.threshold.cardinality?.length && + props.threshold.field.includes(props.threshold.cardinality[0].field) + ) { + errors.push('Cardinality of a field that is being aggregated on is always 1'); + } + if (Array.isArray(props.threshold.field) && props.threshold.field.length > 3) { + errors.push('Number of fields must be 3 or less'); + } + } + } + return errors; +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema.mock.ts similarity index 81% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.mock.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema.mock.ts index 1b3dab2e00691..285e773456d98 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { PatchRulesSchema, ThresholdPatchSchema } from './patch_rules_schema'; +import type { PatchRuleRequestBody, ThresholdPatchRuleRequestBody } from './request_schema'; -export const getPatchRulesSchemaMock = (): PatchRulesSchema => ({ +export const getPatchRulesSchemaMock = (): PatchRuleRequestBody => ({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', @@ -18,7 +18,7 @@ export const getPatchRulesSchemaMock = (): PatchRulesSchema => ({ rule_id: 'rule-1', }); -export const getPatchThresholdRulesSchemaMock = (): ThresholdPatchSchema => ({ +export const getPatchThresholdRulesSchemaMock = (): ThresholdPatchRuleRequestBody => ({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema.test.ts similarity index 80% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema.test.ts index 2084f11ed4efa..36613736e3274 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema.test.ts @@ -5,56 +5,56 @@ * 2.0. */ -import type { PatchRulesSchema } from './patch_rules_schema'; -import { patchRulesSchema } from './patch_rules_schema'; -import { getPatchRulesSchemaMock } from './patch_rules_schema.mock'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { getListArrayMock } from '../types/lists.mock'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { getListArrayMock } from '../../../../../schemas/types/lists.mock'; +import { PatchRuleRequestBody } from './request_schema'; +import { getPatchRulesSchemaMock } from './request_schema.mock'; -describe('patch_rules_schema', () => { +describe('Patch rule request schema', () => { test('[id] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; expect(message.schema).toEqual(expected); }); test('[rule_id] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', }; expect(message.schema).toEqual(expected); }); test('[rule_id, description] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', }; @@ -62,16 +62,16 @@ describe('patch_rules_schema', () => { }); test('[id, description] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', }; @@ -79,16 +79,16 @@ describe('patch_rules_schema', () => { }); test('[id, risk_score] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', risk_score: 10, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', risk_score: 10, }; @@ -96,17 +96,17 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -115,18 +115,18 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', to: 'now', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -136,18 +136,18 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', to: 'now', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -157,7 +157,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, name] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -165,11 +165,11 @@ describe('patch_rules_schema', () => { name: 'some-name', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -180,7 +180,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, name] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -188,11 +188,11 @@ describe('patch_rules_schema', () => { name: 'some-name', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -203,7 +203,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, name, severity] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -212,11 +212,11 @@ describe('patch_rules_schema', () => { severity: 'low', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -228,7 +228,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, name, severity] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -237,11 +237,11 @@ describe('patch_rules_schema', () => { severity: 'low', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -253,7 +253,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, name, severity, type] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -263,11 +263,11 @@ describe('patch_rules_schema', () => { type: 'query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -280,7 +280,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, name, severity, type] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -290,11 +290,11 @@ describe('patch_rules_schema', () => { type: 'query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -307,7 +307,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, name, severity, type, interval] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -318,11 +318,11 @@ describe('patch_rules_schema', () => { type: 'query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -336,7 +336,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, name, severity, type, interval] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -347,11 +347,11 @@ describe('patch_rules_schema', () => { type: 'query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -365,7 +365,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, query] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -378,11 +378,11 @@ describe('patch_rules_schema', () => { query: 'some query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -398,7 +398,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -411,11 +411,11 @@ describe('patch_rules_schema', () => { language: 'kuery', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -431,7 +431,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -439,11 +439,11 @@ describe('patch_rules_schema', () => { name: 'some-name', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -454,7 +454,7 @@ describe('patch_rules_schema', () => { }); test('[id, description, from, to, index, name, severity, type, filters] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -466,11 +466,11 @@ describe('patch_rules_schema', () => { filters: [], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -485,7 +485,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, index, name, severity, type, filters] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -497,11 +497,11 @@ describe('patch_rules_schema', () => { filters: [], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -516,7 +516,7 @@ describe('patch_rules_schema', () => { }); test('allows references to be sent as a valid value to patch with', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -531,11 +531,11 @@ describe('patch_rules_schema', () => { language: 'kuery', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -553,48 +553,48 @@ describe('patch_rules_schema', () => { }); test('does not default references to an array', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchema).references).toEqual(undefined); + expect((message.schema as PatchRuleRequestBody).references).toEqual(undefined); }); test('does not default interval', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchema).interval).toEqual(undefined); + expect((message.schema as PatchRuleRequestBody).interval).toEqual(undefined); }); test('does not default max_signals', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchema).max_signals).toEqual(undefined); + expect((message.schema as PatchRuleRequestBody).max_signals).toEqual(undefined); }); test('references cannot be numbers', () => { - const payload: Omit<PatchRulesSchema, 'references'> & { references: number[] } = { + const payload: Omit<PatchRuleRequestBody, 'references'> & { references: number[] } = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', references: [5], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "references"']); @@ -602,13 +602,13 @@ describe('patch_rules_schema', () => { }); test('indexes cannot be numbers', () => { - const payload: Omit<PatchRulesSchema, 'index'> & { index: number[] } = { + const payload: Omit<PatchRuleRequestBody, 'index'> & { index: number[] } = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', type: 'query', index: [5], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -619,16 +619,16 @@ describe('patch_rules_schema', () => { }); test('saved_id is not required when type is saved_query and will validate without it', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', type: 'saved_query', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', type: 'saved_query', }; @@ -642,7 +642,7 @@ describe('patch_rules_schema', () => { saved_id: 'some id', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -661,7 +661,7 @@ describe('patch_rules_schema', () => { filters: [], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -680,7 +680,7 @@ describe('patch_rules_schema', () => { language: 'kuery', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -699,7 +699,7 @@ describe('patch_rules_schema', () => { language: 'lucene', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -719,7 +719,7 @@ describe('patch_rules_schema', () => { language: 'something-made-up', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -735,7 +735,7 @@ describe('patch_rules_schema', () => { max_signals: -1, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -751,7 +751,7 @@ describe('patch_rules_schema', () => { max_signals: 0, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "0" supplied to "max_signals"']); @@ -765,7 +765,7 @@ describe('patch_rules_schema', () => { max_signals: 1, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -778,16 +778,16 @@ describe('patch_rules_schema', () => { }); test('meta can be patched', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), meta: { whateverYouWant: 'anything_at_all' }, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), meta: { whateverYouWant: 'anything_at_all' }, }; @@ -795,12 +795,12 @@ describe('patch_rules_schema', () => { }); test('You cannot patch meta as a string', () => { - const payload: Omit<PatchRulesSchema, 'meta'> & { meta: string } = { + const payload: Omit<PatchRuleRequestBody, 'meta'> & { meta: string } = { ...getPatchRulesSchemaMock(), meta: 'should not work', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -810,12 +810,12 @@ describe('patch_rules_schema', () => { }); test('filters cannot be a string', () => { - const payload: Omit<PatchRulesSchema, 'filters'> & { filters: string } = { + const payload: Omit<PatchRuleRequestBody, 'filters'> & { filters: string } = { ...getPatchRulesSchemaMock(), filters: 'should not work', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -825,12 +825,12 @@ describe('patch_rules_schema', () => { }); test('name cannot be an empty string', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), name: '', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "name"']); @@ -838,12 +838,12 @@ describe('patch_rules_schema', () => { }); test('description cannot be an empty string', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), description: '', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "description"']); @@ -851,32 +851,32 @@ describe('patch_rules_schema', () => { }); test('threat is not defaulted to empty array on patch', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchema).threat).toEqual(undefined); + expect((message.schema as PatchRuleRequestBody).threat).toEqual(undefined); }); test('threat is not defaulted to undefined on patch with empty array', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', threat: [], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchema).threat).toEqual([]); + expect((message.schema as PatchRuleRequestBody).threat).toEqual([]); }); test('threat is valid when updated with all sub-objects', () => { - const threat: PatchRulesSchema['threat'] = [ + const threat: PatchRuleRequestBody['threat'] = [ { framework: 'fake', tactic: { @@ -893,12 +893,12 @@ describe('patch_rules_schema', () => { ], }, ]; - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', threat, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -906,7 +906,7 @@ describe('patch_rules_schema', () => { }); test('threat is invalid when updated with missing property framework', () => { - const threat: Omit<PatchRulesSchema['threat'], 'framework'> = [ + const threat: Omit<PatchRuleRequestBody['threat'], 'framework'> = [ { tactic: { id: 'fakeId', @@ -922,12 +922,12 @@ describe('patch_rules_schema', () => { ], }, ]; - const payload: Omit<PatchRulesSchema['threat'], 'framework'> = { + const payload: Omit<PatchRuleRequestBody['threat'], 'framework'> = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', threat, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -937,7 +937,7 @@ describe('patch_rules_schema', () => { }); test('threat is invalid when updated with missing tactic sub-object', () => { - const threat: Omit<PatchRulesSchema['threat'], 'tactic'> = [ + const threat: Omit<PatchRuleRequestBody['threat'], 'tactic'> = [ { framework: 'fake', technique: [ @@ -950,12 +950,12 @@ describe('patch_rules_schema', () => { }, ]; - const payload: Omit<PatchRulesSchema['threat'], 'tactic'> = { + const payload: Omit<PatchRuleRequestBody['threat'], 'tactic'> = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', threat, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -965,7 +965,7 @@ describe('patch_rules_schema', () => { }); test('threat is valid when updated with missing technique', () => { - const threat: Omit<PatchRulesSchema['threat'], 'technique'> = [ + const threat: Omit<PatchRuleRequestBody['threat'], 'technique'> = [ { framework: 'fake', tactic: { @@ -976,12 +976,12 @@ describe('patch_rules_schema', () => { }, ]; - const payload: Omit<PatchRulesSchema['threat'], 'technique'> = { + const payload: Omit<PatchRuleRequestBody['threat'], 'technique'> = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', threat, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -989,17 +989,17 @@ describe('patch_rules_schema', () => { }); test('validates with timeline_id and timeline_title', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), timeline_id: 'some-id', timeline_title: 'some-title', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { ...getPatchRulesSchemaMock(), timeline_id: 'some-id', timeline_title: 'some-title', @@ -1008,12 +1008,12 @@ describe('patch_rules_schema', () => { }); test('You cannot set the severity to a value other than low, medium, high, or critical', () => { - const payload: Omit<PatchRulesSchema, 'severity'> & { severity: string } = { + const payload: Omit<PatchRuleRequestBody, 'severity'> & { severity: string } = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', severity: 'junk', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "junk" supplied to "severity"']); @@ -1022,7 +1022,7 @@ describe('patch_rules_schema', () => { describe('note', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, note] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1035,11 +1035,11 @@ describe('patch_rules_schema', () => { note: '# some documentation markdown', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1055,16 +1055,16 @@ describe('patch_rules_schema', () => { }); test('note can be patched', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', note: '# new documentation markdown', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', note: '# new documentation markdown', }; @@ -1072,14 +1072,14 @@ describe('patch_rules_schema', () => { }); test('You cannot patch note as an object', () => { - const payload: Omit<PatchRulesSchema, 'note'> & { note: object } = { + const payload: Omit<PatchRuleRequestBody, 'note'> & { note: object } = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', note: { someProperty: 'something else here', }, }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1090,12 +1090,12 @@ describe('patch_rules_schema', () => { }); test('You cannot send in an array of actions that are missing "group"', () => { - const payload: Omit<PatchRulesSchema['actions'], 'group'> = { + const payload: Omit<PatchRuleRequestBody['actions'], 'group'> = { ...getPatchRulesSchemaMock(), actions: [{ id: 'id', action_type_id: 'action_type_id', params: {} }], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1105,12 +1105,12 @@ describe('patch_rules_schema', () => { }); test('You cannot send in an array of actions that are missing "id"', () => { - const payload: Omit<PatchRulesSchema['actions'], 'id'> = { + const payload: Omit<PatchRuleRequestBody['actions'], 'id'> = { ...getPatchRulesSchemaMock(), actions: [{ group: 'group', action_type_id: 'action_type_id', params: {} }], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1120,12 +1120,12 @@ describe('patch_rules_schema', () => { }); test('You cannot send in an array of actions that are missing "params"', () => { - const payload: Omit<PatchRulesSchema['actions'], 'params'> = { + const payload: Omit<PatchRuleRequestBody['actions'], 'params'> = { ...getPatchRulesSchemaMock(), actions: [{ group: 'group', id: 'id', action_type_id: 'action_type_id' }], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1135,7 +1135,7 @@ describe('patch_rules_schema', () => { }); test('You cannot send in an array of actions that are including "actionTypeId"', () => { - const payload: Omit<PatchRulesSchema['actions'], 'actions'> = { + const payload: Omit<PatchRuleRequestBody['actions'], 'actions'> = { ...getPatchRulesSchemaMock(), actions: [ { @@ -1147,7 +1147,7 @@ describe('patch_rules_schema', () => { ], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1158,7 +1158,7 @@ describe('patch_rules_schema', () => { describe('exception_list', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filters, note, and exceptions_list] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1173,11 +1173,11 @@ describe('patch_rules_schema', () => { exceptions_list: getListArrayMock(), }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1208,7 +1208,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, and empty exceptions_list] does validate', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1224,11 +1224,11 @@ describe('patch_rules_schema', () => { exceptions_list: [], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1263,7 +1263,7 @@ describe('patch_rules_schema', () => { exceptions_list: [{ id: 'uuid_here', namespace_type: 'not a namespace type' }], }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1275,7 +1275,7 @@ describe('patch_rules_schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and non-existent exceptions_list] does validate with empty exceptions_list', () => { - const payload: PatchRulesSchema = { + const payload: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1290,11 +1290,11 @@ describe('patch_rules_schema', () => { note: '# some markdown', }; - const decoded = patchRulesSchema.decode(payload); + const decoded = PatchRuleRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchema = { + const expected: PatchRuleRequestBody = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema.ts new file mode 100644 index 0000000000000..27a1f14687aa4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema.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. + */ + +import { RulePatchProps, ThresholdRulePatchProps } from '../../../../../rule_schema'; + +/** + * Request body parameters of the API route. + * All of the patch elements should default to undefined if not set. + */ +export type PatchRuleRequestBody = RulePatchProps; +export const PatchRuleRequestBody = RulePatchProps; + +export type ThresholdPatchRuleRequestBody = ThresholdRulePatchProps; +export const ThresholdPatchRuleRequestBody = ThresholdRulePatchProps; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema_validation.test.ts new file mode 100644 index 0000000000000..c00e3c38fb91b --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema_validation.test.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PatchRuleRequestBody, ThresholdPatchRuleRequestBody } from './request_schema'; +import { getPatchRulesSchemaMock, getPatchThresholdRulesSchemaMock } from './request_schema.mock'; +import { validatePatchRuleRequestBody } from './request_schema_validation'; + +describe('Patch rule request schema, additional validation', () => { + describe('validatePatchRuleRequestBody', () => { + test('You cannot omit timeline_title when timeline_id is present', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + timeline_id: '123', + }; + delete schema.timeline_title; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); + }); + + test('You cannot have empty string for timeline_title when timeline_id is present', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + timeline_id: '123', + timeline_title: '', + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['"timeline_title" cannot be an empty string']); + }); + + test('You cannot have timeline_title with an empty timeline_id', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + timeline_id: '', + timeline_title: 'some-title', + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['"timeline_id" cannot be an empty string']); + }); + + test('You cannot have timeline_title without timeline_id', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + timeline_title: 'some-title', + }; + delete schema.timeline_id; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); + }); + + test('You cannot have both an id and a rule_id', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + id: 'some-id', + rule_id: 'some-rule-id', + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['both "id" and "rule_id" cannot exist, choose one or the other']); + }); + + test('You must set either an id or a rule_id', () => { + const schema: PatchRuleRequestBody = { + ...getPatchRulesSchemaMock(), + }; + delete schema.id; + delete schema.rule_id; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['either "id" or "rule_id" must be set']); + }); + + test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => { + const schema: ThresholdPatchRuleRequestBody = { + ...getPatchThresholdRulesSchemaMock(), + threshold: { + field: '', + value: -1, + }, + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['"threshold.value" has to be bigger than 0']); + }); + + test('threshold.field should contain 3 items or less', () => { + const schema: ThresholdPatchRuleRequestBody = { + ...getPatchThresholdRulesSchemaMock(), + threshold: { + field: ['field-1', 'field-2', 'field-3', 'field-4'], + value: 1, + }, + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['Number of fields must be 3 or less']); + }); + + test('threshold.cardinality[0].field should not be in threshold.field', () => { + const schema: ThresholdPatchRuleRequestBody = { + ...getPatchThresholdRulesSchemaMock(), + threshold: { + field: ['field-1', 'field-2', 'field-3'], + value: 1, + cardinality: [ + { + field: 'field-1', + value: 2, + }, + ], + }, + }; + const errors = validatePatchRuleRequestBody(schema); + expect(errors).toEqual(['Cardinality of a field that is being aggregated on is always 1']); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema_validation.ts similarity index 80% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema_validation.ts index 263f28e28ac30..72d10bf5e58de 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/patch_rule/request_schema_validation.ts @@ -5,9 +5,31 @@ * 2.0. */ -import type { PatchRulesSchema } from './patch_rules_schema'; +import type { PatchRuleRequestBody } from './request_schema'; -export const validateTimelineId = (rule: PatchRulesSchema): string[] => { +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validatePatchRuleRequestBody = (rule: PatchRuleRequestBody): string[] => { + return [ + ...validateId(rule), + ...validateTimelineId(rule), + ...validateTimelineTitle(rule), + ...validateThreshold(rule), + ]; +}; + +const validateId = (rule: PatchRuleRequestBody): string[] => { + if (rule.id != null && rule.rule_id != null) { + return ['both "id" and "rule_id" cannot exist, choose one or the other']; + } else if (rule.id == null && rule.rule_id == null) { + return ['either "id" or "rule_id" must be set']; + } else { + return []; + } +}; + +const validateTimelineId = (rule: PatchRuleRequestBody): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { return ['when "timeline_id" exists, "timeline_title" must also exist']; @@ -20,7 +42,7 @@ export const validateTimelineId = (rule: PatchRulesSchema): string[] => { return []; }; -export const validateTimelineTitle = (rule: PatchRulesSchema): string[] => { +const validateTimelineTitle = (rule: PatchRuleRequestBody): string[] => { if (rule.timeline_title != null) { if (rule.timeline_id == null) { return ['when "timeline_title" exists, "timeline_id" must also exist']; @@ -33,17 +55,7 @@ export const validateTimelineTitle = (rule: PatchRulesSchema): string[] => { return []; }; -export const validateId = (rule: PatchRulesSchema): string[] => { - if (rule.id != null && rule.rule_id != null) { - return ['both "id" and "rule_id" cannot exist, choose one or the other']; - } else if (rule.id == null && rule.rule_id == null) { - return ['either "id" or "rule_id" must be set']; - } else { - return []; - } -}; - -export const validateThreshold = (rule: PatchRulesSchema): string[] => { +const validateThreshold = (rule: PatchRuleRequestBody): string[] => { const errors: string[] = []; if (rule.type === 'threshold') { if (!rule.threshold) { @@ -65,12 +77,3 @@ export const validateThreshold = (rule: PatchRulesSchema): string[] => { } return errors; }; - -export const patchRuleValidateTypeDependents = (rule: PatchRulesSchema): string[] => { - return [ - ...validateId(rule), - ...validateTimelineId(rule), - ...validateTimelineTitle(rule), - ...validateThreshold(rule), - ]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids.test.ts similarity index 72% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids.test.ts index faccffb1e6864..d5ba9b37ae65a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids.test.ts @@ -5,17 +5,16 @@ * 2.0. */ -import type { QueryRulesSchema } from './query_rules_schema'; -import { queryRulesSchema } from './query_rules_schema'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; +import { QueryRuleByIds } from './query_rule_by_ids'; -describe('query_rules_schema', () => { +describe('Query rule by IDs schema', () => { test('empty objects do validate', () => { - const payload: Partial<QueryRulesSchema> = {}; + const payload: Partial<QueryRuleByIds> = {}; - const decoded = queryRulesSchema.decode(payload); + const decoded = QueryRuleByIds.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids.ts similarity index 56% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids.ts index 704c2307181b9..d52d8c66125e6 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids.ts @@ -6,15 +6,12 @@ */ import * as t from 'io-ts'; +import { RuleObjectId, RuleSignatureId } from '../../../../../rule_schema'; -import { rule_id, id } from '../common/schemas'; - -export const queryRulesSchema = t.exact( +export type QueryRuleByIds = t.TypeOf<typeof QueryRuleByIds>; +export const QueryRuleByIds = t.exact( t.partial({ - rule_id, - id, + rule_id: RuleSignatureId, + id: RuleObjectId, }) ); - -export type QueryRulesSchema = t.TypeOf<typeof queryRulesSchema>; -export type QueryRulesSchemaDecoded = QueryRulesSchema; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids_validation.test.ts similarity index 61% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_type_dependents.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids_validation.test.ts index 060fd189a40a9..1d0981df5f411 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids_validation.test.ts @@ -5,22 +5,22 @@ * 2.0. */ -import type { QueryRulesSchema } from './query_rules_schema'; -import { queryRuleValidateTypeDependents } from './query_rules_type_dependents'; +import type { QueryRuleByIds } from './query_rule_by_ids'; +import { validateQueryRuleByIds } from './query_rule_by_ids_validation'; -describe('query_rules_type_dependents', () => { +describe('Query rule by IDs schema, additional validation', () => { test('You cannot have both an id and a rule_id', () => { - const schema: QueryRulesSchema = { + const schema: QueryRuleByIds = { id: 'some-id', rule_id: 'some-rule-id', }; - const errors = queryRuleValidateTypeDependents(schema); + const errors = validateQueryRuleByIds(schema); expect(errors).toEqual(['both "id" and "rule_id" cannot exist, choose one or the other']); }); test('You must set either an id or a rule_id', () => { - const schema: QueryRulesSchema = {}; - const errors = queryRuleValidateTypeDependents(schema); + const schema: QueryRuleByIds = {}; + const errors = validateQueryRuleByIds(schema); expect(errors).toEqual(['either "id" or "rule_id" must be set']); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids_validation.ts similarity index 66% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_type_dependents.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids_validation.ts index a646d0e97f96a..4f0a7eedc71dd 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/read_rule/query_rule_by_ids_validation.ts @@ -5,9 +5,16 @@ * 2.0. */ -import type { QueryRulesSchema } from './query_rules_schema'; +import type { QueryRuleByIds } from './query_rule_by_ids'; -export const validateId = (rule: QueryRulesSchema): string[] => { +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validateQueryRuleByIds = (schema: QueryRuleByIds): string[] => { + return [...validateId(schema)]; +}; + +const validateId = (rule: QueryRuleByIds): string[] => { if (rule.id != null && rule.rule_id != null) { return ['both "id" and "rule_id" cannot exist, choose one or the other']; } else if (rule.id == null && rule.rule_id == null) { @@ -16,7 +23,3 @@ export const validateId = (rule: QueryRulesSchema): string[] => { return []; } }; - -export const queryRuleValidateTypeDependents = (schema: QueryRulesSchema): string[] => { - return [...validateId(schema)]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/update_rule/request_schema_validation.test.ts similarity index 68% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_type_dependents.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/update_rule/request_schema_validation.test.ts index e2f5024418005..f6a8d0bb39340 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/update_rule/request_schema_validation.test.ts @@ -5,68 +5,68 @@ * 2.0. */ -import { getUpdateRulesSchemaMock } from './rule_schemas.mock'; -import type { UpdateRulesSchema } from './rule_schemas'; -import { updateRuleValidateTypeDependents } from './update_rules_type_dependents'; +import type { RuleUpdateProps } from '../../../../../rule_schema'; +import { getUpdateRulesSchemaMock } from '../../../../../rule_schema/mocks'; +import { validateUpdateRuleProps } from './request_schema_validation'; -describe('update_rules_type_dependents', () => { +describe('Update rule request schema, additional validation', () => { test('You cannot omit timeline_title when timeline_id is present', () => { - const schema: UpdateRulesSchema = { + const schema: RuleUpdateProps = { ...getUpdateRulesSchemaMock(), timeline_id: '123', }; delete schema.timeline_title; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleProps(schema); expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); }); test('You cannot have empty string for timeline_title when timeline_id is present', () => { - const schema: UpdateRulesSchema = { + const schema: RuleUpdateProps = { ...getUpdateRulesSchemaMock(), timeline_id: '123', timeline_title: '', }; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleProps(schema); expect(errors).toEqual(['"timeline_title" cannot be an empty string']); }); test('You cannot have timeline_title with an empty timeline_id', () => { - const schema: UpdateRulesSchema = { + const schema: RuleUpdateProps = { ...getUpdateRulesSchemaMock(), timeline_id: '', timeline_title: 'some-title', }; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleProps(schema); expect(errors).toEqual(['"timeline_id" cannot be an empty string']); }); test('You cannot have timeline_title without timeline_id', () => { - const schema: UpdateRulesSchema = { + const schema: RuleUpdateProps = { ...getUpdateRulesSchemaMock(), timeline_title: 'some-title', }; delete schema.timeline_id; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleProps(schema); expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); }); test('You cannot have both an id and a rule_id', () => { - const schema: UpdateRulesSchema = { + const schema: RuleUpdateProps = { ...getUpdateRulesSchemaMock(), id: 'some-id', rule_id: 'some-rule-id', }; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleProps(schema); expect(errors).toEqual(['both "id" and "rule_id" cannot exist, choose one or the other']); }); test('You must set either an id or a rule_id', () => { - const schema: UpdateRulesSchema = { + const schema: RuleUpdateProps = { ...getUpdateRulesSchemaMock(), }; delete schema.id; delete schema.rule_id; - const errors = updateRuleValidateTypeDependents(schema); + const errors = validateUpdateRuleProps(schema); expect(errors).toEqual(['either "id" or "rule_id" must be set']); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/update_rule/request_schema_validation.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/update_rule/request_schema_validation.ts new file mode 100644 index 0000000000000..09d1e1e7b0a99 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/crud/update_rule/request_schema_validation.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 type { RuleUpdateProps } from '../../../../../rule_schema'; + +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validateUpdateRuleProps = (props: RuleUpdateProps): string[] => { + return [ + ...validateId(props), + ...validateTimelineId(props), + ...validateTimelineTitle(props), + ...validateThreshold(props), + ]; +}; + +const validateId = (props: RuleUpdateProps): string[] => { + if (props.id != null && props.rule_id != null) { + return ['both "id" and "rule_id" cannot exist, choose one or the other']; + } else if (props.id == null && props.rule_id == null) { + return ['either "id" or "rule_id" must be set']; + } else { + return []; + } +}; + +const validateTimelineId = (props: RuleUpdateProps): string[] => { + if (props.timeline_id != null) { + if (props.timeline_title == null) { + return ['when "timeline_id" exists, "timeline_title" must also exist']; + } else if (props.timeline_id === '') { + return ['"timeline_id" cannot be an empty string']; + } else { + return []; + } + } + return []; +}; + +const validateTimelineTitle = (props: RuleUpdateProps): string[] => { + if (props.timeline_title != null) { + if (props.timeline_id == null) { + return ['when "timeline_title" exists, "timeline_id" must also exist']; + } else if (props.timeline_title === '') { + return ['"timeline_title" cannot be an empty string']; + } else { + return []; + } + } + return []; +}; + +const validateThreshold = (props: RuleUpdateProps): string[] => { + const errors: string[] = []; + if (props.type === 'threshold') { + if (!props.threshold) { + errors.push('when "type" is "threshold", "threshold" is required'); + } else { + if ( + props.threshold.cardinality?.length && + props.threshold.field.includes(props.threshold.cardinality[0].field) + ) { + errors.push('Cardinality of a field that is being aggregated on is always 1'); + } + if (Array.isArray(props.threshold.field) && props.threshold.field.length > 3) { + errors.push('Number of fields must be 3 or less'); + } + } + } + return errors; +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/export_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.test.ts similarity index 72% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/export_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.test.ts index d2b29a5152e96..0e0ec3cbaca3b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/export_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.test.ts @@ -5,22 +5,19 @@ * 2.0. */ -import type { - ExportRulesSchema, - ExportRulesQuerySchema, - ExportRulesQuerySchemaDecoded, -} from './export_rules_schema'; -import { exportRulesQuerySchema, exportRulesSchema } from './export_rules_schema'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { ExportRulesRequestBody, ExportRulesRequestQuery } from './request_schema'; +import type { ExportRulesRequestQueryDecoded } from './request_schema'; -describe('create rules schema', () => { - describe('exportRulesSchema', () => { +describe('Export rules request schema', () => { + describe('ExportRulesRequestBody', () => { test('null value or absent values validate', () => { - const payload: Partial<ExportRulesSchema> = null; + const payload: Partial<ExportRulesRequestBody> = null; - const decoded = exportRulesSchema.decode(payload); + const decoded = ExportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -30,7 +27,7 @@ describe('create rules schema', () => { test('empty object does not validate', () => { const payload = {}; - const decoded = exportRulesSchema.decode(payload); + const decoded = ExportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -41,9 +38,9 @@ describe('create rules schema', () => { }); test('empty object array does validate', () => { - const payload: ExportRulesSchema = { objects: [] }; + const payload: ExportRulesRequestBody = { objects: [] }; - const decoded = exportRulesSchema.decode(payload); + const decoded = ExportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -51,9 +48,9 @@ describe('create rules schema', () => { }); test('array with rule_id validates', () => { - const payload: ExportRulesSchema = { objects: [{ rule_id: 'test-1' }] }; + const payload: ExportRulesRequestBody = { objects: [{ rule_id: 'test-1' }] }; - const decoded = exportRulesSchema.decode(payload); + const decoded = ExportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -61,11 +58,11 @@ describe('create rules schema', () => { }); test('array with id does not validate as we do not allow that on purpose since we export rule_id', () => { - const payload: Omit<ExportRulesSchema, 'objects'> & { objects: [{ id: string }] } = { + const payload: Omit<ExportRulesRequestBody, 'objects'> & { objects: [{ id: string }] } = { objects: [{ id: '4a7ff83d-3055-4bb2-ba68-587b9c6c15a4' }], }; - const decoded = exportRulesSchema.decode(payload); + const decoded = ExportRulesRequestBody.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -76,15 +73,15 @@ describe('create rules schema', () => { }); }); - describe('exportRulesQuerySchema', () => { + describe('ExportRulesRequestQuery', () => { test('default value for file_name is export.ndjson and default for exclude_export_details is false', () => { - const payload: Partial<ExportRulesQuerySchema> = {}; + const payload: Partial<ExportRulesRequestQuery> = {}; - const decoded = exportRulesQuerySchema.decode(payload); + const decoded = ExportRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ExportRulesQuerySchemaDecoded = { + const expected: ExportRulesRequestQueryDecoded = { file_name: 'export.ndjson', exclude_export_details: false, }; @@ -93,15 +90,15 @@ describe('create rules schema', () => { }); test('file_name validates', () => { - const payload: ExportRulesQuerySchema = { + const payload: ExportRulesRequestQuery = { file_name: 'test.ndjson', }; - const decoded = exportRulesQuerySchema.decode(payload); + const decoded = ExportRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ExportRulesQuerySchemaDecoded = { + const expected: ExportRulesRequestQueryDecoded = { file_name: 'test.ndjson', exclude_export_details: false, }; @@ -110,11 +107,11 @@ describe('create rules schema', () => { }); test('file_name does not validate with a number', () => { - const payload: Omit<ExportRulesQuerySchema, 'file_name'> & { file_name: number } = { + const payload: Omit<ExportRulesRequestQuery, 'file_name'> & { file_name: number } = { file_name: 10, }; - const decoded = exportRulesQuerySchema.decode(payload); + const decoded = ExportRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -124,15 +121,15 @@ describe('create rules schema', () => { }); test('exclude_export_details validates with a boolean true', () => { - const payload: ExportRulesQuerySchema = { + const payload: ExportRulesRequestQuery = { exclude_export_details: true, }; - const decoded = exportRulesQuerySchema.decode(payload); + const decoded = ExportRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ExportRulesQuerySchemaDecoded = { + const expected: ExportRulesRequestQueryDecoded = { exclude_export_details: true, file_name: 'export.ndjson', }; @@ -140,13 +137,13 @@ describe('create rules schema', () => { }); test('exclude_export_details does not validate with a string', () => { - const payload: Omit<ExportRulesQuerySchema, 'exclude_export_details'> & { + const payload: Omit<ExportRulesRequestQuery, 'exclude_export_details'> & { exclude_export_details: string; } = { exclude_export_details: 'invalid string', }; - const decoded = exportRulesQuerySchema.decode(payload); + const decoded = ExportRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.ts new file mode 100644 index 0000000000000..682e69e55d5a4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/export_rules/request_schema.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 * as t from 'io-ts'; +import { DefaultExportFileName } from '@kbn/securitysolution-io-ts-alerting-types'; +import { DefaultStringBooleanFalse } from '@kbn/securitysolution-io-ts-types'; + +import type { FileName, ExcludeExportDetails } from '../../../../schemas/common/schemas'; +import { RuleSignatureId } from '../../../../rule_schema'; + +const ObjectsWithRuleId = t.array(t.exact(t.type({ rule_id: RuleSignatureId }))); + +/** + * Request body parameters of the API route. + */ +export type ExportRulesRequestBody = t.TypeOf<typeof ExportRulesRequestBody>; +export const ExportRulesRequestBody = t.union([ + t.exact(t.type({ objects: ObjectsWithRuleId })), + t.null, +]); + +/** + * Query string parameters of the API route. + */ +export type ExportRulesRequestQuery = t.TypeOf<typeof ExportRulesRequestQuery>; +export const ExportRulesRequestQuery = t.exact( + t.partial({ file_name: DefaultExportFileName, exclude_export_details: DefaultStringBooleanFalse }) +); + +export type ExportRulesRequestQueryDecoded = Omit< + ExportRulesRequestQuery, + 'file_name' | 'exclude_export_details' +> & { + file_name: FileName; + exclude_export_details: ExcludeExportDetails; +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.test.ts similarity index 63% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.test.ts index 41e765c8c268b..67a3d045d746d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.test.ts @@ -5,17 +5,17 @@ * 2.0. */ -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import type { FindRulesSchema } from './find_rules_schema'; -import { findRulesSchema } from './find_rules_schema'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { FindRulesRequestQuery } from './request_schema'; -describe('find_rules_schema', () => { +describe('Find rules request schema', () => { test('empty objects do validate', () => { - const payload: FindRulesSchema = {}; + const payload: FindRulesRequestQuery = {}; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -26,7 +26,7 @@ describe('find_rules_schema', () => { }); test('all values validate', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { per_page: 5, page: 1, sort_field: 'some field', @@ -35,7 +35,7 @@ describe('find_rules_schema', () => { sort_order: 'asc', }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -43,9 +43,11 @@ describe('find_rules_schema', () => { }); test('made up parameters do not validate', () => { - const payload: Partial<FindRulesSchema> & { madeUp: string } = { madeUp: 'invalid value' }; + const payload: Partial<FindRulesRequestQuery> & { madeUp: string } = { + madeUp: 'invalid value', + }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); @@ -53,71 +55,71 @@ describe('find_rules_schema', () => { }); test('per_page validates', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { per_page: 5, }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).per_page).toEqual(payload.per_page); + expect((message.schema as FindRulesRequestQuery).per_page).toEqual(payload.per_page); }); test('page validates', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { page: 5, }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).page).toEqual(payload.page); + expect((message.schema as FindRulesRequestQuery).page).toEqual(payload.page); }); test('sort_field validates', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { sort_field: 'value', }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).sort_field).toEqual('value'); + expect((message.schema as FindRulesRequestQuery).sort_field).toEqual('value'); }); test('fields validates with a string', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { fields: ['some value'], }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).fields).toEqual(payload.fields); + expect((message.schema as FindRulesRequestQuery).fields).toEqual(payload.fields); }); test('fields validates with multiple strings', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { fields: ['some value 1', 'some value 2'], }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).fields).toEqual(payload.fields); + expect((message.schema as FindRulesRequestQuery).fields).toEqual(payload.fields); }); test('fields does not validate with a number', () => { - const payload: Omit<FindRulesSchema, 'fields'> & { fields: number } = { + const payload: Omit<FindRulesRequestQuery, 'fields'> & { fields: number } = { fields: 5, }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "fields"']); @@ -125,43 +127,43 @@ describe('find_rules_schema', () => { }); test('per_page has a default of 20', () => { - const payload: FindRulesSchema = {}; + const payload: FindRulesRequestQuery = {}; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).per_page).toEqual(20); + expect((message.schema as FindRulesRequestQuery).per_page).toEqual(20); }); test('page has a default of 1', () => { - const payload: FindRulesSchema = {}; + const payload: FindRulesRequestQuery = {}; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).page).toEqual(1); + expect((message.schema as FindRulesRequestQuery).page).toEqual(1); }); test('filter works with a string', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { filter: 'some value 1', }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).filter).toEqual(payload.filter); + expect((message.schema as FindRulesRequestQuery).filter).toEqual(payload.filter); }); test('filter does not work with a number', () => { - const payload: Omit<FindRulesSchema, 'filter'> & { filter: number } = { + const payload: Omit<FindRulesRequestQuery, 'filter'> & { filter: number } = { filter: 5, }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "filter"']); @@ -169,26 +171,26 @@ describe('find_rules_schema', () => { }); test('sort_order validates with desc and sort_field', () => { - const payload: FindRulesSchema = { + const payload: FindRulesRequestQuery = { sort_order: 'desc', sort_field: 'some field', }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as FindRulesSchema).sort_order).toEqual(payload.sort_order); - expect((message.schema as FindRulesSchema).sort_field).toEqual(payload.sort_field); + expect((message.schema as FindRulesRequestQuery).sort_order).toEqual(payload.sort_order); + expect((message.schema as FindRulesRequestQuery).sort_field).toEqual(payload.sort_field); }); test('sort_order does not validate with a string other than asc and desc', () => { - const payload: Omit<FindRulesSchema, 'sort_order'> & { sort_order: string } = { + const payload: Omit<FindRulesRequestQuery, 'sort_order'> & { sort_order: string } = { sort_order: 'some other string', sort_field: 'some field', }; - const decoded = findRulesSchema.decode(payload); + const decoded = FindRulesRequestQuery.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.ts similarity index 59% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.ts index 39f0105a2a88f..9b321d443b2de 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema.ts @@ -6,12 +6,15 @@ */ import * as t from 'io-ts'; - import { DefaultPerPage, DefaultPage } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { PerPage, Page } from '../common'; -import { queryFilter, fields, SortField, SortOrder } from '../common'; +import type { PerPage, Page } from '../../../../schemas/common'; +import { queryFilter, fields, SortField, SortOrder } from '../../../../schemas/common'; -export const findRulesSchema = t.exact( +/** + * Query string parameters of the API route. + */ +export type FindRulesRequestQuery = t.TypeOf<typeof FindRulesRequestQuery>; +export const FindRulesRequestQuery = t.exact( t.partial({ fields, filter: queryFilter, @@ -22,8 +25,7 @@ export const findRulesSchema = t.exact( }) ); -export type FindRulesSchema = t.TypeOf<typeof findRulesSchema>; -export type FindRulesSchemaDecoded = Omit<FindRulesSchema, 'per_page'> & { +export type FindRulesRequestQueryDecoded = Omit<FindRulesRequestQuery, 'per_page'> & { page: Page; per_page: PerPage; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.test.ts new file mode 100644 index 0000000000000..862bf7cc1a350 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.test.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 { FindRulesRequestQuery } from './request_schema'; +import { validateFindRulesRequestQuery } from './request_schema_validation'; + +describe('Find rules request schema, additional validation', () => { + describe('validateFindRulesRequestQuery', () => { + test('You can have an empty sort_field and empty sort_order', () => { + const schema: FindRulesRequestQuery = {}; + const errors = validateFindRulesRequestQuery(schema); + expect(errors).toEqual([]); + }); + + test('You can have both a sort_field and and a sort_order', () => { + const schema: FindRulesRequestQuery = { + sort_field: 'some field', + sort_order: 'asc', + }; + const errors = validateFindRulesRequestQuery(schema); + expect(errors).toEqual([]); + }); + + test('You cannot have sort_field without sort_order', () => { + const schema: FindRulesRequestQuery = { + sort_field: 'some field', + }; + const errors = validateFindRulesRequestQuery(schema); + expect(errors).toEqual([ + 'when "sort_order" and "sort_field" must exist together or not at all', + ]); + }); + + test('You cannot have sort_order without sort_field', () => { + const schema: FindRulesRequestQuery = { + sort_order: 'asc', + }; + const errors = validateFindRulesRequestQuery(schema); + expect(errors).toEqual([ + 'when "sort_order" and "sort_field" must exist together or not at all', + ]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.ts new file mode 100644 index 0000000000000..a8ceb09dea5b4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/find_rules/request_schema_validation.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 type { FindRulesRequestQuery } from './request_schema'; + +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validateFindRulesRequestQuery = (query: FindRulesRequestQuery): string[] => { + return [...validateSortOrder(query)]; +}; + +const validateSortOrder = (query: FindRulesRequestQuery): string[] => { + if (query.sort_order != null || query.sort_field != null) { + if (query.sort_order == null || query.sort_field == null) { + return ['when "sort_order" and "sort_field" must exist together or not at all']; + } else { + return []; + } + } else { + return []; + } +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.ts new file mode 100644 index 0000000000000..bd63dd7472aaf --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.test.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 { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { ImportRulesRequestBody } from './request_schema'; + +describe('Import rules schema', () => { + describe('ImportRulesRequestBody', () => { + test('does not validate with an empty object', () => { + const payload = {}; + + const decoded = ImportRulesRequestBody.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "file"', + ]); + expect(message.schema).toEqual({}); + }); + + test('does not validate with a made string', () => { + const payload: Omit<ImportRulesRequestBody, 'file'> & { madeUpKey: string } = { + madeUpKey: 'madeupstring', + }; + + const decoded = ImportRulesRequestBody.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "file"', + ]); + expect(message.schema).toEqual({}); + }); + + test('does validate with a file object', () => { + const payload: ImportRulesRequestBody = { file: {} }; + + const decoded = ImportRulesRequestBody.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rule_by_id_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts similarity index 56% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rule_by_id_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts index 44b9692e7977f..8f64df3ea71b1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rule_by_id_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/request_schema.ts @@ -7,13 +7,12 @@ import * as t from 'io-ts'; -import { id } from '../common/schemas'; - -export const queryRuleByIdSchema = t.exact( +/** + * Request body parameters of the API route. + */ +export type ImportRulesRequestBody = t.TypeOf<typeof ImportRulesRequestBody>; +export const ImportRulesRequestBody = t.exact( t.type({ - id, + file: t.object, }) ); - -export type QueryRuleByIdSchema = t.TypeOf<typeof queryRuleByIdSchema>; -export type QueryRuleByIdSchemaDecoded = QueryRuleByIdSchema; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.test.ts similarity index 82% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.test.ts index db1fc33e9d44e..2f11e1a9c120f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.test.ts @@ -8,15 +8,15 @@ import { pipe } from 'fp-ts/lib/pipeable'; import type { Either } from 'fp-ts/lib/Either'; import { left } from 'fp-ts/lib/Either'; -import type { ImportRulesSchema } from './import_rules_schema'; -import { importRulesSchema } from './import_rules_schema'; -import type { ErrorSchema } from './error_schema'; import type { Errors } from 'io-ts'; + import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; +import type { ErrorSchema } from '../../../../schemas/response/error_schema'; +import { ImportRulesResponse } from './response_schema'; -describe('import_rules_schema', () => { +describe('Import rules response schema', () => { test('it should validate an empty import response with no errors', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: true, success_count: 0, rules_count: 0, @@ -25,7 +25,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -34,7 +34,7 @@ describe('import_rules_schema', () => { }); test('it should validate an empty import response with a single error', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: 0, rules_count: 0, @@ -43,7 +43,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -52,7 +52,7 @@ describe('import_rules_schema', () => { }); test('it should validate an empty import response with a single exceptions error', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: 0, rules_count: 0, @@ -61,7 +61,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -70,7 +70,7 @@ describe('import_rules_schema', () => { }); test('it should validate an empty import response with two errors', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: 0, rules_count: 0, @@ -82,7 +82,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -91,7 +91,7 @@ describe('import_rules_schema', () => { }); test('it should validate an empty import response with two exception errors', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: 0, rules_count: 0, @@ -103,7 +103,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -112,7 +112,7 @@ describe('import_rules_schema', () => { }); test('it should NOT validate a success_count that is a negative number', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: -1, rules_count: 0, @@ -121,7 +121,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -132,7 +132,7 @@ describe('import_rules_schema', () => { }); test('it should NOT validate a exceptions_success_count that is a negative number', () => { - const payload: ImportRulesSchema = { + const payload: ImportRulesResponse = { success: false, success_count: 0, rules_count: 0, @@ -141,7 +141,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: -1, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -170,7 +170,7 @@ describe('import_rules_schema', () => { >; } >; - const payload: Omit<ImportRulesSchema, 'success'> & { success: string } = { + const payload: Omit<ImportRulesResponse, 'success'> & { success: string } = { success: 'hello', success_count: 0, rules_count: 0, @@ -179,7 +179,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded as UnsafeCastForTest); const message = pipe(checked, foldLeftRight); @@ -207,17 +207,18 @@ describe('import_rules_schema', () => { >; } >; - const payload: Omit<ImportRulesSchema, 'exceptions_success'> & { exceptions_success: string } = - { - success: true, - success_count: 0, - rules_count: 0, - errors: [], - exceptions_errors: [], - exceptions_success: 'hello', - exceptions_success_count: 0, - }; - const decoded = importRulesSchema.decode(payload); + const payload: Omit<ImportRulesResponse, 'exceptions_success'> & { + exceptions_success: string; + } = { + success: true, + success_count: 0, + rules_count: 0, + errors: [], + exceptions_errors: [], + exceptions_success: 'hello', + exceptions_success_count: 0, + }; + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded as UnsafeCastForTest); const message = pipe(checked, foldLeftRight); @@ -228,7 +229,7 @@ describe('import_rules_schema', () => { }); test('it should NOT validate a success an extra invalid field', () => { - const payload: ImportRulesSchema & { invalid_field: string } = { + const payload: ImportRulesResponse & { invalid_field: string } = { success: true, success_count: 0, rules_count: 0, @@ -238,7 +239,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -248,7 +249,7 @@ describe('import_rules_schema', () => { test('it should NOT validate an extra field in the second position of the errors array', () => { type InvalidError = ErrorSchema & { invalid_data?: string }; - const payload: Omit<ImportRulesSchema, 'errors'> & { + const payload: Omit<ImportRulesResponse, 'errors'> & { errors: InvalidError[]; } = { success: true, @@ -262,7 +263,7 @@ describe('import_rules_schema', () => { exceptions_success: true, exceptions_success_count: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = ImportRulesResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.ts similarity index 69% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.ts index d577b2135d58d..77ccd0812c2c9 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/import_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/import_rules/response_schema.ts @@ -5,22 +5,19 @@ * 2.0. */ -import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; import * as t from 'io-ts'; +import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; +import { errorSchema } from '../../../../schemas/response/error_schema'; -import { success, success_count } from '../common/schemas'; -import { errorSchema } from './error_schema'; - -export const importRulesSchema = t.exact( +export type ImportRulesResponse = t.TypeOf<typeof ImportRulesResponse>; +export const ImportRulesResponse = t.exact( t.type({ exceptions_success: t.boolean, exceptions_success_count: PositiveInteger, exceptions_errors: t.array(errorSchema), rules_count: PositiveInteger, - success, - success_count, + success: t.boolean, + success_count: PositiveInteger, errors: t.array(errorSchema), }) ); - -export type ImportRulesSchema = t.TypeOf<typeof importRulesSchema>; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/urls.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/urls.ts new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/urls.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/plugins/security_solution/common/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts new file mode 100644 index 0000000000000..266a7e30d311c --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/index.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './api/rules/bulk_crud/bulk_create_rules/request_schema'; +export * from './api/rules/bulk_crud/bulk_delete_rules/request_schema'; +export * from './api/rules/bulk_crud/bulk_patch_rules/request_schema'; +export * from './api/rules/bulk_crud/bulk_update_rules/request_schema'; +export * from './api/rules/bulk_crud/response_schema'; + +export * from './api/rules/crud/create_rule/request_schema_validation'; +export * from './api/rules/crud/patch_rule/request_schema_validation'; +export * from './api/rules/crud/patch_rule/request_schema'; +export * from './api/rules/crud/read_rule/query_rule_by_ids_validation'; +export * from './api/rules/crud/read_rule/query_rule_by_ids'; +export * from './api/rules/crud/update_rule/request_schema_validation'; + +export * from './api/rules/export_rules/request_schema'; +export * from './api/rules/find_rules/request_schema_validation'; +export * from './api/rules/find_rules/request_schema'; +export * from './api/rules/import_rules/request_schema'; +export * from './api/rules/import_rules/response_schema'; + +// TODO: https://github.com/elastic/kibana/pull/142950 +// export * from './api/urls'; + +export * from './model/export/export_rules_details_schema'; +export * from './model/import/rule_to_import_validation'; +export * from './model/import/rule_to_import'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts new file mode 100644 index 0000000000000..6827236f8dcbf --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/mocks.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './api/rules/bulk_actions/request_schema.mock'; +export * from './api/rules/crud/patch_rule/request_schema.mock'; + +export * from './model/export/export_rules_details_schema.mock'; +export * from './model/import/rule_to_import.mock'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.mock.ts similarity index 100% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.mock.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.mock.ts index 9b79238bef6c7..64b82abdb3755 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { ExportRulesDetails } from './export_rules_details_schema'; -import type { ExportExceptionDetailsMock } from '@kbn/lists-plugin/common/schemas/response/exception_export_details_schema.mock'; import { getExceptionExportDetailsMock } from '@kbn/lists-plugin/common/schemas/response/exception_export_details_schema.mock'; +import type { ExportExceptionDetailsMock } from '@kbn/lists-plugin/common/schemas/response/exception_export_details_schema.mock'; +import type { ExportRulesDetails } from './export_rules_details_schema'; interface RuleDetailsMock { totalCount?: number; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.test.ts similarity index 100% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.test.ts diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.ts similarity index 96% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.ts index 05df728aa3f5c..85b423135566b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/export_rules_details_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/export/export_rules_details_schema.ts @@ -16,7 +16,7 @@ const createSchema = <Required extends t.Props, Optional extends t.Props>( return t.intersection([t.exact(t.type(requiredFields)), t.exact(t.partial(optionalFields))]); }; -export const exportRulesDetails = { +const exportRulesDetails = { exported_count: t.number, exported_rules_count: t.number, missing_rules: t.array( diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.mock.ts similarity index 88% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.mock.ts index c469926104131..d1dc9e8ac4663 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import type { ImportRulesSchema } from './import_rules_schema'; +import type { RuleToImport } from './rule_to_import'; -export const getImportRulesSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema => ({ +export const getImportRulesSchemaMock = (ruleId = 'rule-1'): RuleToImport => ({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', @@ -18,7 +18,7 @@ export const getImportRulesSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema = rule_id: ruleId, }); -export const getImportRulesWithIdSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema => ({ +export const getImportRulesWithIdSchemaMock = (ruleId = 'rule-1'): RuleToImport => ({ id: '6afb8ce1-ea94-4790-8653-fd0b021d2113', description: 'some description', name: 'Query with a rule id', @@ -35,7 +35,7 @@ export const getImportRulesWithIdSchemaMock = (ruleId = 'rule-1'): ImportRulesSc * as we might import/export * @param rules Array of rule objects with which to generate rule JSON */ -export const rulesToNdJsonString = (rules: ImportRulesSchema[]) => { +export const rulesToNdJsonString = (rules: RuleToImport[]) => { return rules.map((rule) => JSON.stringify(rule)).join('\r\n'); }; @@ -49,7 +49,7 @@ export const ruleIdsToNdJsonString = (ruleIds: string[]) => { return rulesToNdJsonString(rules); }; -export const getImportThreatMatchRulesSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema => ({ +export const getImportThreatMatchRulesSchemaMock = (ruleId = 'rule-1'): RuleToImport => ({ description: 'some description', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.test.ts similarity index 78% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.test.ts index 1c119e696e7b7..84478b4af27fe 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.test.ts @@ -5,22 +5,22 @@ * 2.0. */ -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import type { ImportRulesPayloadSchema, ImportRulesSchema } from './import_rules_schema'; -import { importRulesPayloadSchema, importRulesSchema } from './import_rules_schema'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { getListArrayMock } from '../../../schemas/types/lists.mock'; +import { RuleToImport } from './rule_to_import'; import { getImportRulesSchemaMock, getImportThreatMatchRulesSchemaMock, -} from './import_rules_schema.mock'; -import { getListArrayMock } from '../types/lists.mock'; +} from './rule_to_import.mock'; -describe('import rules schema', () => { +describe('RuleToImport', () => { test('empty objects do not validate', () => { - const payload: Partial<ImportRulesSchema> = {}; + const payload: Partial<RuleToImport> = {}; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -42,12 +42,12 @@ describe('import rules schema', () => { }); test('made up values do not validate', () => { - const payload: ImportRulesSchema & { madeUp: string } = { + const payload: RuleToImport & { madeUp: string } = { ...getImportRulesSchemaMock(), madeUp: 'hi', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); @@ -55,11 +55,11 @@ describe('import rules schema', () => { }); test('[rule_id] does not validate', () => { - const payload: Partial<ImportRulesSchema> = { + const payload: Partial<RuleToImport> = { rule_id: 'rule-1', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -78,12 +78,12 @@ describe('import rules schema', () => { }); test('[rule_id, description] does not validate', () => { - const payload: Partial<ImportRulesSchema> = { + const payload: Partial<RuleToImport> = { rule_id: 'rule-1', description: 'some description', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -99,13 +99,13 @@ describe('import rules schema', () => { }); test('[rule_id, description, from] does not validate', () => { - const payload: Partial<ImportRulesSchema> = { + const payload: Partial<RuleToImport> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -121,14 +121,14 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to] does not validate', () => { - const payload: Partial<ImportRulesSchema> = { + const payload: Partial<RuleToImport> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', to: 'now', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -144,7 +144,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name] does not validate', () => { - const payload: Partial<ImportRulesSchema> = { + const payload: Partial<RuleToImport> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -152,7 +152,7 @@ describe('import rules schema', () => { name: 'some-name', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -165,7 +165,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name, severity] does not validate', () => { - const payload: Partial<ImportRulesSchema> = { + const payload: Partial<RuleToImport> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -174,7 +174,7 @@ describe('import rules schema', () => { severity: 'low', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toContain( @@ -184,7 +184,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type] does not validate', () => { - const payload: Partial<ImportRulesSchema> = { + const payload: Partial<RuleToImport> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -194,7 +194,7 @@ describe('import rules schema', () => { type: 'query', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -204,7 +204,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { - const payload: Partial<ImportRulesSchema> = { + const payload: Partial<RuleToImport> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -215,7 +215,7 @@ describe('import rules schema', () => { type: 'query', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -225,7 +225,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { - const payload: Partial<ImportRulesSchema> = { + const payload: Partial<RuleToImport> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -237,7 +237,7 @@ describe('import rules schema', () => { index: ['index-1'], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -247,7 +247,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -261,14 +261,14 @@ describe('import rules schema', () => { interval: '5m', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { - const payload: Partial<ImportRulesSchema> = { + const payload: Partial<RuleToImport> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -282,7 +282,7 @@ describe('import rules schema', () => { language: 'kuery', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -292,7 +292,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -307,14 +307,14 @@ describe('import rules schema', () => { language: 'kuery', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -330,14 +330,14 @@ describe('import rules schema', () => { language: 'kuery', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -350,14 +350,14 @@ describe('import rules schema', () => { risk_score: 50, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -371,26 +371,26 @@ describe('import rules schema', () => { type: 'query', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can send in an empty array to threat', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), threat: [], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threat] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -421,19 +421,19 @@ describe('import rules schema', () => { ], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('allows references to be sent as valid', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), references: ['index-1'], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -441,19 +441,19 @@ describe('import rules schema', () => { test('defaults references to an array if it is not sent in', () => { const { references, ...noReferences } = getImportRulesSchemaMock(); - const decoded = importRulesSchema.decode(noReferences); + const decoded = RuleToImport.decode(noReferences); const checked = exactCheck(noReferences, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('references cannot be numbers', () => { - const payload: Omit<ImportRulesSchema, 'references'> & { references: number[] } = { + const payload: Omit<RuleToImport, 'references'> & { references: number[] } = { ...getImportRulesSchemaMock(), references: [5], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "references"']); @@ -461,12 +461,12 @@ describe('import rules schema', () => { }); test('indexes cannot be numbers', () => { - const payload: Omit<ImportRulesSchema, 'index'> & { index: number[] } = { + const payload: Omit<RuleToImport, 'index'> & { index: number[] } = { ...getImportRulesSchemaMock(), index: [5], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "index"']); @@ -475,11 +475,11 @@ describe('import rules schema', () => { test('defaults interval to 5 min', () => { const { interval, ...noInterval } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noInterval, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -488,11 +488,11 @@ describe('import rules schema', () => { test('defaults max signals to 100', () => { // eslint-disable-next-line @typescript-eslint/naming-convention const { max_signals, ...noMaxSignals } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noMaxSignals, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -504,19 +504,19 @@ describe('import rules schema', () => { filters: [], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('filters cannot be a string', () => { - const payload: Omit<ImportRulesSchema, 'filters'> & { filters: string } = { + const payload: Omit<RuleToImport, 'filters'> & { filters: string } = { ...getImportRulesSchemaMock(), filters: 'some string', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -531,7 +531,7 @@ describe('import rules schema', () => { language: 'kuery', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -543,19 +543,19 @@ describe('import rules schema', () => { language: 'lucene', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('language does not validate with something made up', () => { - const payload: Omit<ImportRulesSchema, 'language'> & { language: string } = { + const payload: Omit<RuleToImport, 'language'> & { language: string } = { ...getImportRulesSchemaMock(), language: 'something-made-up', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -565,12 +565,12 @@ describe('import rules schema', () => { }); test('max_signals cannot be negative', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), max_signals: -1, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -580,12 +580,12 @@ describe('import rules schema', () => { }); test('max_signals cannot be zero', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), max_signals: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "0" supplied to "max_signals"']); @@ -593,36 +593,36 @@ describe('import rules schema', () => { }); test('max_signals can be 1', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), max_signals: 1, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can optionally send in an array of tags', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), tags: ['tag_1', 'tag_2'], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot send in an array of tags that are numbers', () => { - const payload: Omit<ImportRulesSchema, 'tags'> & { tags: number[] } = { + const payload: Omit<RuleToImport, 'tags'> & { tags: number[] } = { ...getImportRulesSchemaMock(), tags: [0, 1, 2], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -634,8 +634,8 @@ describe('import rules schema', () => { }); test('You cannot send in an array of threat that are missing "framework"', () => { - const payload: Omit<ImportRulesSchema, 'threat'> & { - threat: Array<Partial<Omit<ImportRulesSchema['threat'], 'framework'>>>; + const payload: Omit<RuleToImport, 'threat'> & { + threat: Array<Partial<Omit<RuleToImport['threat'], 'framework'>>>; } = { ...getImportRulesSchemaMock(), threat: [ @@ -656,7 +656,7 @@ describe('import rules schema', () => { ], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -666,8 +666,8 @@ describe('import rules schema', () => { }); test('You cannot send in an array of threat that are missing "tactic"', () => { - const payload: Omit<ImportRulesSchema, 'threat'> & { - threat: Array<Partial<Omit<ImportRulesSchema['threat'], 'tactic'>>>; + const payload: Omit<RuleToImport, 'threat'> & { + threat: Array<Partial<Omit<RuleToImport['threat'], 'tactic'>>>; } = { ...getImportRulesSchemaMock(), threat: [ @@ -684,7 +684,7 @@ describe('import rules schema', () => { ], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -694,8 +694,8 @@ describe('import rules schema', () => { }); test('You can send in an array of threat that are missing "technique"', () => { - const payload: Omit<ImportRulesSchema, 'threat'> & { - threat: Array<Partial<Omit<ImportRulesSchema['threat'], 'technique'>>>; + const payload: Omit<RuleToImport, 'threat'> & { + threat: Array<Partial<Omit<RuleToImport['threat'], 'technique'>>>; } = { ...getImportRulesSchemaMock(), threat: [ @@ -710,31 +710,31 @@ describe('import rules schema', () => { ], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can optionally send in an array of false positives', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), false_positives: ['false_1', 'false_2'], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot send in an array of false positives that are numbers', () => { - const payload: Omit<ImportRulesSchema, 'false_positives'> & { false_positives: number[] } = { + const payload: Omit<RuleToImport, 'false_positives'> & { false_positives: number[] } = { ...getImportRulesSchemaMock(), false_positives: [5, 4], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -745,12 +745,12 @@ describe('import rules schema', () => { }); test('You cannot set the immutable to a number when trying to create a rule', () => { - const payload: Omit<ImportRulesSchema, 'immutable'> & { immutable: number } = { + const payload: Omit<RuleToImport, 'immutable'> & { immutable: number } = { ...getImportRulesSchemaMock(), immutable: 5, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "immutable"']); @@ -758,24 +758,24 @@ describe('import rules schema', () => { }); test('You can optionally set the immutable to be false', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), immutable: false, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot set the immutable to be true', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), immutable: true, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -785,12 +785,12 @@ describe('import rules schema', () => { }); test('You cannot set the immutable to be a number', () => { - const payload: Omit<ImportRulesSchema, 'immutable'> & { immutable: number } = { + const payload: Omit<RuleToImport, 'immutable'> & { immutable: number } = { ...getImportRulesSchemaMock(), immutable: 5, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "immutable"']); @@ -798,12 +798,12 @@ describe('import rules schema', () => { }); test('You cannot set the risk_score to 101', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), risk_score: 101, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -813,12 +813,12 @@ describe('import rules schema', () => { }); test('You cannot set the risk_score to -1', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), risk_score: -1, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "-1" supplied to "risk_score"']); @@ -826,50 +826,50 @@ describe('import rules schema', () => { }); test('You can set the risk_score to 0', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), risk_score: 0, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can set the risk_score to 100', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), risk_score: 100, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can set meta to any object you want', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), meta: { somethingMadeUp: { somethingElse: true }, }, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot create meta as a string', () => { - const payload: Omit<ImportRulesSchema, 'meta'> & { meta: string } = { + const payload: Omit<RuleToImport, 'meta'> & { meta: string } = { ...getImportRulesSchemaMock(), meta: 'should not work', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -879,27 +879,27 @@ describe('import rules schema', () => { }); test('validates with timeline_id and timeline_title', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), timeline_id: 'timeline-id', timeline_title: 'timeline-title', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('rule_id is required and you cannot get by with just id', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), id: 'c4e80a0d-e20f-4efc-84c1-08112da5a612', }; // @ts-expect-error delete payload.rule_id; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -909,7 +909,7 @@ describe('import rules schema', () => { }); test('it validates with created_at, updated_at, created_by, updated_by values', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), created_at: '2020-01-09T06:15:24.749Z', updated_at: '2020-01-09T06:15:24.749Z', @@ -917,19 +917,19 @@ describe('import rules schema', () => { updated_by: 'Evan Hassanabad', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('it does not validate with epoch strings for created_at', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), created_at: '1578550728650', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -939,12 +939,12 @@ describe('import rules schema', () => { }); test('it does not validate with epoch strings for updated_at', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), updated_at: '1578550728650', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -953,51 +953,13 @@ describe('import rules schema', () => { expect(message.schema).toEqual({}); }); - describe('importRulesPayloadSchema', () => { - test('does not validate with an empty object', () => { - const payload = {}; - - const decoded = importRulesPayloadSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "file"', - ]); - expect(message.schema).toEqual({}); - }); - - test('does not validate with a made string', () => { - const payload: Omit<ImportRulesPayloadSchema, 'file'> & { madeUpKey: string } = { - madeUpKey: 'madeupstring', - }; - - const decoded = importRulesPayloadSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "file"', - ]); - expect(message.schema).toEqual({}); - }); - - test('does validate with a file object', () => { - const payload: ImportRulesPayloadSchema = { file: {} }; - - const decoded = importRulesPayloadSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - }); - test('The default for "from" will be "now-6m"', () => { const { from, ...noFrom } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noFrom, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1005,23 +967,23 @@ describe('import rules schema', () => { test('The default for "to" will be "now"', () => { const { to, ...noTo } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noTo, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot set the severity to a value other than low, medium, high, or critical', () => { - const payload: Omit<ImportRulesSchema, 'severity'> & { severity: string } = { + const payload: Omit<RuleToImport, 'severity'> & { severity: string } = { ...getImportRulesSchemaMock(), severity: 'junk', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "junk" supplied to "severity"']); @@ -1030,22 +992,23 @@ describe('import rules schema', () => { test('The default for "actions" will be an empty array', () => { const { actions, ...noActions } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noActions, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); + test('You cannot send in an array of actions that are missing "group"', () => { - const payload: Omit<ImportRulesSchema['actions'], 'group'> = { + const payload: Omit<RuleToImport['actions'], 'group'> = { ...getImportRulesSchemaMock(), actions: [{ id: 'id', action_type_id: 'action_type_id', params: {} }], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1055,12 +1018,12 @@ describe('import rules schema', () => { }); test('You cannot send in an array of actions that are missing "id"', () => { - const payload: Omit<ImportRulesSchema['actions'], 'id'> = { + const payload: Omit<RuleToImport['actions'], 'id'> = { ...getImportRulesSchemaMock(), actions: [{ group: 'group', action_type_id: 'action_type_id', params: {} }], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1070,12 +1033,12 @@ describe('import rules schema', () => { }); test('You cannot send in an array of actions that are missing "action_type_id"', () => { - const payload: Omit<ImportRulesSchema['actions'], 'action_type_id'> = { + const payload: Omit<RuleToImport['actions'], 'action_type_id'> = { ...getImportRulesSchemaMock(), actions: [{ group: 'group', id: 'id', params: {} }], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1085,12 +1048,12 @@ describe('import rules schema', () => { }); test('You cannot send in an array of actions that are missing "params"', () => { - const payload: Omit<ImportRulesSchema['actions'], 'params'> = { + const payload: Omit<RuleToImport['actions'], 'params'> = { ...getImportRulesSchemaMock(), actions: [{ group: 'group', id: 'id', action_type_id: 'action_type_id' }], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1100,7 +1063,7 @@ describe('import rules schema', () => { }); test('You cannot send in an array of actions that are including "actionTypeId"', () => { - const payload: Omit<ImportRulesSchema['actions'], 'actions'> = { + const payload: Omit<RuleToImport['actions'], 'actions'> = { ...getImportRulesSchemaMock(), actions: [ { @@ -1112,7 +1075,7 @@ describe('import rules schema', () => { ], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1123,11 +1086,11 @@ describe('import rules schema', () => { test('The default for "throttle" will be null', () => { const { throttle, ...noThrottle } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...noThrottle, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1135,38 +1098,38 @@ describe('import rules schema', () => { describe('note', () => { test('You can set note to a string', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), note: '# documentation markdown here', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You can set note to an empty string', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { ...getImportRulesSchemaMock(), note: '', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('You cannot create note as an object', () => { - const payload: Omit<ImportRulesSchema, 'note'> & { note: {} } = { + const payload: Omit<RuleToImport, 'note'> & { note: {} } = { ...getImportRulesSchemaMock(), note: { somethingHere: 'something else', }, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1176,7 +1139,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1190,7 +1153,7 @@ describe('import rules schema', () => { note: '# some markdown', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1199,7 +1162,7 @@ describe('import rules schema', () => { describe('exception_list', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and exceptions_list] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1215,14 +1178,14 @@ describe('import rules schema', () => { exceptions_list: getListArrayMock(), }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, and empty exceptions_list] does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1238,7 +1201,7 @@ describe('import rules schema', () => { exceptions_list: [], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1261,7 +1224,7 @@ describe('import rules schema', () => { exceptions_list: [{ id: 'uuid_here', namespace_type: 'not a namespace type' }], }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1273,7 +1236,7 @@ describe('import rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and non-existent exceptions_list] does validate with empty exceptions_list', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1288,7 +1251,7 @@ describe('import rules schema', () => { note: '# some markdown', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1298,7 +1261,7 @@ describe('import rules schema', () => { describe('threat_mapping', () => { test('You can set a threat query, index, mapping, filters on an imported rule', () => { const payload = getImportThreatMatchRulesSchemaMock(); - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1307,7 +1270,7 @@ describe('import rules schema', () => { describe('data_view_id', () => { test('Defined data_view_id and empty index does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1322,7 +1285,7 @@ describe('import rules schema', () => { interval: '5m', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1330,7 +1293,7 @@ describe('import rules schema', () => { // Both can be defined, but if a data_view_id is defined, rule will use that one test('Defined data_view_id and index does validate', () => { - const payload: ImportRulesSchema = { + const payload: RuleToImport = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1345,19 +1308,19 @@ describe('import rules schema', () => { interval: '5m', }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); }); test('data_view_id cannot be a number', () => { - const payload: Omit<ImportRulesSchema, 'data_view_id'> & { data_view_id: number } = { + const payload: Omit<RuleToImport, 'data_view_id'> & { data_view_id: number } = { ...getImportRulesSchemaMock(), data_view_id: 5, }; - const decoded = importRulesSchema.decode(payload); + const decoded = RuleToImport.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.ts similarity index 69% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.ts index b3d533a167a7a..b40bed8ce65c5 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import.ts @@ -6,20 +6,18 @@ */ import * as t from 'io-ts'; - import { OnlyFalseAllowed } from '@kbn/securitysolution-io-ts-types'; + import { - rule_id, - id, - created_at, - updated_at, - created_by, - updated_by, RelatedIntegrationArray, RequiredFieldArray, + RuleObjectId, + RuleSignatureId, SetupGuide, -} from '../common'; -import { baseCreateParams, createTypeSpecific } from './rule_schemas'; + BaseCreateProps, + TypeSpecificCreateProps, +} from '../../../rule_schema'; +import { created_at, updated_at, created_by, updated_by } from '../../../schemas/common'; /** * Differences from this and the createRulesSchema are @@ -31,13 +29,14 @@ import { baseCreateParams, createTypeSpecific } from './rule_schemas'; * - created_by is optional (but ignored in the import code) * - updated_by is optional (but ignored in the import code) */ -export const importRulesSchema = t.intersection([ - baseCreateParams, - createTypeSpecific, - t.exact(t.type({ rule_id })), +export type RuleToImport = t.TypeOf<typeof RuleToImport>; +export const RuleToImport = t.intersection([ + BaseCreateProps, + TypeSpecificCreateProps, + t.exact(t.type({ rule_id: RuleSignatureId })), t.exact( t.partial({ - id, + id: RuleObjectId, immutable: OnlyFalseAllowed, updated_at, updated_by, @@ -49,13 +48,3 @@ export const importRulesSchema = t.intersection([ }) ), ]); - -export type ImportRulesSchema = t.TypeOf<typeof importRulesSchema>; - -export const importRulesPayloadSchema = t.exact( - t.type({ - file: t.object, - }) -); - -export type ImportRulesPayloadSchema = t.TypeOf<typeof importRulesPayloadSchema>; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.test.ts new file mode 100644 index 0000000000000..31ac993eb4053 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { RuleToImport } from './rule_to_import'; +import { getImportRulesSchemaMock } from './rule_to_import.mock'; +import { validateRuleToImport } from './rule_to_import_validation'; + +describe('Rule to import schema, additional validation', () => { + describe('validateRuleToImport', () => { + test('You cannot omit timeline_title when timeline_id is present', () => { + const schema: RuleToImport = { + ...getImportRulesSchemaMock(), + timeline_id: '123', + }; + delete schema.timeline_title; + const errors = validateRuleToImport(schema); + expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); + }); + + test('You cannot have empty string for timeline_title when timeline_id is present', () => { + const schema: RuleToImport = { + ...getImportRulesSchemaMock(), + timeline_id: '123', + timeline_title: '', + }; + const errors = validateRuleToImport(schema); + expect(errors).toEqual(['"timeline_title" cannot be an empty string']); + }); + + test('You cannot have timeline_title with an empty timeline_id', () => { + const schema: RuleToImport = { + ...getImportRulesSchemaMock(), + timeline_id: '', + timeline_title: 'some-title', + }; + const errors = validateRuleToImport(schema); + expect(errors).toEqual(['"timeline_id" cannot be an empty string']); + }); + + test('You cannot have timeline_title without timeline_id', () => { + const schema: RuleToImport = { + ...getImportRulesSchemaMock(), + timeline_title: 'some-title', + }; + delete schema.timeline_id; + const errors = validateRuleToImport(schema); + expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.ts similarity index 78% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.ts index bdb025583b404..de21ac3a7964c 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/model/import/rule_to_import_validation.ts @@ -5,9 +5,16 @@ * 2.0. */ -import type { ImportRulesSchema } from './import_rules_schema'; +import type { RuleToImport } from './rule_to_import'; -export const validateTimelineId = (rule: ImportRulesSchema): string[] => { +/** + * Additional validation that is implemented outside of the schema itself. + */ +export const validateRuleToImport = (rule: RuleToImport): string[] => { + return [...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule)]; +}; + +const validateTimelineId = (rule: RuleToImport): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { return ['when "timeline_id" exists, "timeline_title" must also exist']; @@ -20,7 +27,7 @@ export const validateTimelineId = (rule: ImportRulesSchema): string[] => { return []; }; -export const validateTimelineTitle = (rule: ImportRulesSchema): string[] => { +const validateTimelineTitle = (rule: RuleToImport): string[] => { if (rule.timeline_title != null) { if (rule.timeline_id == null) { return ['when "timeline_title" exists, "timeline_id" must also exist']; @@ -33,7 +40,7 @@ export const validateTimelineTitle = (rule: ImportRulesSchema): string[] => { return []; }; -export const validateThreshold = (rule: ImportRulesSchema): string[] => { +const validateThreshold = (rule: RuleToImport): string[] => { const errors: string[] = []; if (rule.type === 'threshold') { if ( @@ -48,7 +55,3 @@ export const validateThreshold = (rule: ImportRulesSchema): string[] => { } return errors; }; - -export const importRuleValidateTypeDependents = (rule: ImportRulesSchema): string[] => { - return [...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule)]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_monitoring/api/get_rule_execution_events/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_monitoring/api/get_rule_execution_events/request_schema.ts index 9ffa2467e0852..59e17a9d6f604 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/rule_monitoring/api/get_rule_execution_events/request_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_monitoring/api/get_rule_execution_events/request_schema.ts @@ -15,7 +15,7 @@ import { TRuleExecutionEventType } from '../../model/execution_event'; import { TLogLevel } from '../../model/log_level'; /** - * Path parameters of the API route. + * URL path parameters of the API route. */ export type GetRuleExecutionEventsRequestParams = t.TypeOf< typeof GetRuleExecutionEventsRequestParams diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/index.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/index.ts new file mode 100644 index 0000000000000..cf1266b1b9a71 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './model/common_attributes/field_overrides'; +export * from './model/common_attributes/misc_attributes'; +export * from './model/common_attributes/related_integrations'; +export * from './model/common_attributes/required_fields'; +export * from './model/common_attributes/saved_objects'; +export * from './model/common_attributes/timeline_template'; + +export * from './model/specific_attributes/eql_attributes'; +export * from './model/specific_attributes/new_terms_attributes'; +export * from './model/specific_attributes/threshold_attributes'; + +export * from './model/rule_schemas'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/mocks.ts new file mode 100644 index 0000000000000..6cf0d49e9560a --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/mocks.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 * from './model/rule_request_schema.mock'; +export * from './model/rule_response_schema.mock'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/build_rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/build_rule_schemas.ts new file mode 100644 index 0000000000000..f7d52c682d191 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/build_rule_schemas.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 * as t from 'io-ts'; + +interface RuleFields< + Required extends t.Props, + Optional extends t.Props, + Defaultable extends t.Props +> { + required: Required; + optional: Optional; + defaultable: Defaultable; +} + +export const buildRuleSchemas = <R extends t.Props, O extends t.Props, D extends t.Props>( + fields: RuleFields<R, O, D> +) => { + return { + create: buildCreateRuleSchema(fields.required, fields.optional, fields.defaultable), + patch: buildPatchRuleSchema(fields.required, fields.optional, fields.defaultable), + response: buildResponseRuleSchema(fields.required, fields.optional, fields.defaultable), + }; +}; + +const buildCreateRuleSchema = < + Required extends t.Props, + Optional extends t.Props, + Defaultable extends t.Props +>( + requiredFields: Required, + optionalFields: Optional, + defaultableFields: Defaultable +) => { + return t.intersection([ + t.exact(t.type(requiredFields)), + t.exact(t.partial(optionalFields)), + t.exact(t.partial(defaultableFields)), + ]); +}; + +const buildPatchRuleSchema = < + Required extends t.Props, + Optional extends t.Props, + Defaultable extends t.Props +>( + requiredFields: Required, + optionalFields: Optional, + defaultableFields: Defaultable +) => { + return t.intersection([ + t.partial(requiredFields), + t.partial(optionalFields), + t.partial(defaultableFields), + ]); +}; + +type OrUndefined<P extends t.Props> = { + [K in keyof P]: P[K] | t.UndefinedC; +}; + +export const buildResponseRuleSchema = < + Required extends t.Props, + Optional extends t.Props, + Defaultable extends t.Props +>( + requiredFields: Required, + optionalFields: Optional, + defaultableFields: Defaultable +) => { + // This bit of logic is to force all fields to be accounted for in conversions from the internal + // rule schema to the response schema. Rather than use `t.partial`, which makes each field optional, + // we make each field required but possibly undefined. The result is that if a field is forgotten in + // the conversion from internal schema to response schema TS will report an error. If we just used t.partial + // instead, then optional fields can be accidentally omitted from the conversion - and any actual values + // in those fields internally will be stripped in the response. + const optionalWithUndefined = Object.keys(optionalFields).reduce<t.Props>((acc, key) => { + acc[key] = t.union([optionalFields[key], t.undefined]); + return acc; + }, {}) as OrUndefined<Optional>; + return t.intersection([ + t.exact(t.type(requiredFields)), + t.exact(t.type(optionalWithUndefined)), + t.exact(t.type(defaultableFields)), + ]); +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/field_overrides.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/field_overrides.ts new file mode 100644 index 0000000000000..85058099fdadf --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/field_overrides.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 * as t from 'io-ts'; + +export type RuleNameOverride = t.TypeOf<typeof RuleNameOverride>; +export const RuleNameOverride = t.string; // should be non-empty string? + +export type TimestampOverride = t.TypeOf<typeof TimestampOverride>; +export const TimestampOverride = t.string; // should be non-empty string? + +export type TimestampOverrideFallbackDisabled = t.TypeOf<typeof TimestampOverrideFallbackDisabled>; +export const TimestampOverrideFallbackDisabled = t.boolean; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/misc_attributes.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/misc_attributes.ts new file mode 100644 index 0000000000000..315a15190ec28 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/misc_attributes.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; +import { listArray } from '@kbn/securitysolution-io-ts-list-types'; +import { NonEmptyString, version, UUID } from '@kbn/securitysolution-io-ts-types'; +import { max_signals, threat } from '@kbn/securitysolution-io-ts-alerting-types'; + +export type RuleObjectId = t.TypeOf<typeof RuleObjectId>; +export const RuleObjectId = UUID; + +/** + * NOTE: Never make this a strict uuid, we allow the rule_id to be any string at the moment + * in case we encounter 3rd party rule systems which might be using auto incrementing numbers + * or other different things. + */ +export type RuleSignatureId = t.TypeOf<typeof RuleSignatureId>; +export const RuleSignatureId = t.string; // should be non-empty string? + +export type RuleName = t.TypeOf<typeof RuleName>; +export const RuleName = NonEmptyString; + +export type RuleDescription = t.TypeOf<typeof RuleDescription>; +export const RuleDescription = NonEmptyString; + +export type RuleVersion = t.TypeOf<typeof RuleVersion>; +export const RuleVersion = version; + +export type IsRuleImmutable = t.TypeOf<typeof IsRuleImmutable>; +export const IsRuleImmutable = t.boolean; + +export type IsRuleEnabled = t.TypeOf<typeof IsRuleEnabled>; +export const IsRuleEnabled = t.boolean; + +export type RuleTagArray = t.TypeOf<typeof RuleTagArray>; +export const RuleTagArray = t.array(t.string); // should be non-empty strings? + +/** + * Note that this is a non-exact io-ts type as we allow extra meta information + * to be added to the meta object + */ +export type RuleMetadata = t.TypeOf<typeof RuleMetadata>; +export const RuleMetadata = t.object; // should be a more specific type? + +export type RuleLicense = t.TypeOf<typeof RuleLicense>; +export const RuleLicense = t.string; // should be non-empty string? + +export type RuleAuthorArray = t.TypeOf<typeof RuleAuthorArray>; +export const RuleAuthorArray = t.array(t.string); // should be non-empty strings? + +export type RuleFalsePositiveArray = t.TypeOf<typeof RuleFalsePositiveArray>; +export const RuleFalsePositiveArray = t.array(t.string); // should be non-empty strings? + +export type RuleReferenceArray = t.TypeOf<typeof RuleReferenceArray>; +export const RuleReferenceArray = t.array(t.string); // should be non-empty strings? + +export type InvestigationGuide = t.TypeOf<typeof InvestigationGuide>; +export const InvestigationGuide = t.string; + +/** + * Any instructions for the user for setting up their environment in order to start receiving + * source events for a given rule. + * + * It's a multiline text. Markdown is supported. + */ +export type SetupGuide = t.TypeOf<typeof SetupGuide>; +export const SetupGuide = t.string; + +export type BuildingBlockType = t.TypeOf<typeof BuildingBlockType>; +export const BuildingBlockType = t.string; + +export type AlertsIndex = t.TypeOf<typeof AlertsIndex>; +export const AlertsIndex = t.string; + +export type AlertsIndexNamespace = t.TypeOf<typeof AlertsIndexNamespace>; +export const AlertsIndexNamespace = t.string; + +export type ExceptionListArray = t.TypeOf<typeof ExceptionListArray>; +export const ExceptionListArray = listArray; + +export type MaxSignals = t.TypeOf<typeof MaxSignals>; +export const MaxSignals = max_signals; + +export type ThreatArray = t.TypeOf<typeof ThreatArray>; +export const ThreatArray = t.array(threat); + +export type IndexPatternArray = t.TypeOf<typeof IndexPatternArray>; +export const IndexPatternArray = t.array(t.string); + +export type DataViewId = t.TypeOf<typeof DataViewId>; +export const DataViewId = t.string; + +export type RuleQuery = t.TypeOf<typeof RuleQuery>; +export const RuleQuery = t.string; + +/** + * TODO: Right now the filters is an "unknown", when it could more than likely + * become the actual ESFilter as a type. + */ +export type RuleFilterArray = t.TypeOf<typeof RuleFilterArray>; // Filters are not easily type-able yet +export const RuleFilterArray = t.array(t.unknown); // Filters are not easily type-able yet diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/rule_params.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/related_integrations.ts similarity index 55% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/common/rule_params.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/related_integrations.ts index d65bce6e587ef..d99043d81e19e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/rule_params.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/related_integrations.ts @@ -8,9 +8,6 @@ import * as t from 'io-ts'; import { NonEmptyString } from '@kbn/securitysolution-io-ts-types'; -// ------------------------------------------------------------------------------------------------- -// Related integrations - /** * Related integration is a potential dependency of a rule. It's assumed that if the user installs * one of the related integrations of a rule, the rule might start to work properly because it will @@ -74,72 +71,3 @@ export const RelatedIntegration = t.exact( */ export type RelatedIntegrationArray = t.TypeOf<typeof RelatedIntegrationArray>; export const RelatedIntegrationArray = t.array(RelatedIntegration); - -// ------------------------------------------------------------------------------------------------- -// Required fields - -/** - * Almost all types of Security rules check source event documents for a match to some kind of - * query or filter. If a document has certain field with certain values, then it's a match and - * the rule will generate an alert. - * - * Required field is an event field that must be present in the source indices of a given rule. - * - * @example - * const standardEcsField: RequiredField = { - * name: 'event.action', - * type: 'keyword', - * ecs: true, - * }; - * - * @example - * const nonEcsField: RequiredField = { - * name: 'winlog.event_data.AttributeLDAPDisplayName', - * type: 'keyword', - * ecs: false, - * }; - */ -export const RequiredField = t.exact( - t.type({ - name: NonEmptyString, - type: NonEmptyString, - ecs: t.boolean, - }) -); - -/** - * Array of event fields that must be present in the source indices of a given rule. - * - * @example - * const x: RequiredFieldArray = [ - * { - * name: 'event.action', - * type: 'keyword', - * ecs: true, - * }, - * { - * name: 'event.code', - * type: 'keyword', - * ecs: true, - * }, - * { - * name: 'winlog.event_data.AttributeLDAPDisplayName', - * type: 'keyword', - * ecs: false, - * }, - * ]; - */ -export type RequiredFieldArray = t.TypeOf<typeof RequiredFieldArray>; -export const RequiredFieldArray = t.array(RequiredField); - -// ------------------------------------------------------------------------------------------------- -// Setup guide - -/** - * Any instructions for the user for setting up their environment in order to start receiving - * source events for a given rule. - * - * It's a multiline text. Markdown is supported. - */ -export type SetupGuide = t.TypeOf<typeof SetupGuide>; -export const SetupGuide = t.string; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/required_fields.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/required_fields.ts new file mode 100644 index 0000000000000..0938612fd4654 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/required_fields.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 * as t from 'io-ts'; +import { NonEmptyString } from '@kbn/securitysolution-io-ts-types'; + +/** + * Almost all types of Security rules check source event documents for a match to some kind of + * query or filter. If a document has certain field with certain values, then it's a match and + * the rule will generate an alert. + * + * Required field is an event field that must be present in the source indices of a given rule. + * + * @example + * const standardEcsField: RequiredField = { + * name: 'event.action', + * type: 'keyword', + * ecs: true, + * }; + * + * @example + * const nonEcsField: RequiredField = { + * name: 'winlog.event_data.AttributeLDAPDisplayName', + * type: 'keyword', + * ecs: false, + * }; + */ +export type RequiredField = t.TypeOf<typeof RequiredField>; +export const RequiredField = t.exact( + t.type({ + name: NonEmptyString, + type: NonEmptyString, + ecs: t.boolean, + }) +); + +/** + * Array of event fields that must be present in the source indices of a given rule. + * + * @example + * const x: RequiredFieldArray = [ + * { + * name: 'event.action', + * type: 'keyword', + * ecs: true, + * }, + * { + * name: 'event.code', + * type: 'keyword', + * ecs: true, + * }, + * { + * name: 'winlog.event_data.AttributeLDAPDisplayName', + * type: 'keyword', + * ecs: false, + * }, + * ]; + */ +export type RequiredFieldArray = t.TypeOf<typeof RequiredFieldArray>; +export const RequiredFieldArray = t.array(RequiredField); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/saved_objects.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/saved_objects.ts new file mode 100644 index 0000000000000..78d2eb1d9813a --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/saved_objects.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 * as t from 'io-ts'; + +/** + * Outcome is a property of the saved object resolve api + * will tell us info about the rule after 8.0 migrations + */ +export type SavedObjectResolveOutcome = t.TypeOf<typeof SavedObjectResolveOutcome>; +export const SavedObjectResolveOutcome = t.union([ + t.literal('exactMatch'), + t.literal('aliasMatch'), + t.literal('conflict'), +]); + +export type SavedObjectResolveAliasTargetId = t.TypeOf<typeof SavedObjectResolveAliasTargetId>; +export const SavedObjectResolveAliasTargetId = t.string; + +export type SavedObjectResolveAliasPurpose = t.TypeOf<typeof SavedObjectResolveAliasPurpose>; +export const SavedObjectResolveAliasPurpose = t.union([ + t.literal('savedObjectConversion'), + t.literal('savedObjectImport'), +]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/timeline_template.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/timeline_template.ts new file mode 100644 index 0000000000000..da3f0c1c210bd --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/common_attributes/timeline_template.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 * as t from 'io-ts'; + +export type TimelineTemplateId = t.TypeOf<typeof TimelineTemplateId>; +export const TimelineTemplateId = t.string; // should be non-empty string? + +export type TimelineTemplateTitle = t.TypeOf<typeof TimelineTemplateTitle>; +export const TimelineTemplateTitle = t.string; // should be non-empty string? diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_request_schema.mock.ts similarity index 86% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_request_schema.mock.ts index 5edc868a8836c..d76450a0e0425 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_request_schema.mock.ts @@ -7,18 +7,18 @@ import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../constants'; import type { - MachineLearningCreateSchema, - MachineLearningUpdateSchema, - QueryCreateSchema, - QueryUpdateSchema, - SavedQueryCreateSchema, - ThreatMatchCreateSchema, - ThresholdCreateSchema, - NewTermsCreateSchema, - NewTermsUpdateSchema, + MachineLearningRuleCreateProps, + MachineLearningRuleUpdateProps, + QueryRuleCreateProps, + QueryRuleUpdateProps, + SavedQueryRuleCreateProps, + ThreatMatchRuleCreateProps, + ThresholdRuleCreateProps, + NewTermsRuleCreateProps, + NewTermsRuleUpdateProps, } from './rule_schemas'; -export const getCreateRulesSchemaMock = (ruleId = 'rule-1'): QueryCreateSchema => ({ +export const getCreateRulesSchemaMock = (ruleId = 'rule-1'): QueryRuleCreateProps => ({ description: 'Detecting root and admin users', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', @@ -29,7 +29,7 @@ export const getCreateRulesSchemaMock = (ruleId = 'rule-1'): QueryCreateSchema = rule_id: ruleId, }); -export const getCreateRulesSchemaMockWithDataView = (ruleId = 'rule-1'): QueryCreateSchema => ({ +export const getCreateRulesSchemaMockWithDataView = (ruleId = 'rule-1'): QueryRuleCreateProps => ({ data_view_id: 'logs-*', description: 'Detecting root and admin users', name: 'Query with a rule id', @@ -41,7 +41,9 @@ export const getCreateRulesSchemaMockWithDataView = (ruleId = 'rule-1'): QueryCr rule_id: ruleId, }); -export const getCreateSavedQueryRulesSchemaMock = (ruleId = 'rule-1'): SavedQueryCreateSchema => ({ +export const getCreateSavedQueryRulesSchemaMock = ( + ruleId = 'rule-1' +): SavedQueryRuleCreateProps => ({ description: 'Detecting root and admin users', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', @@ -56,7 +58,7 @@ export const getCreateSavedQueryRulesSchemaMock = (ruleId = 'rule-1'): SavedQuer export const getCreateThreatMatchRulesSchemaMock = ( ruleId = 'rule-1', enabled = false -): ThreatMatchCreateSchema => ({ +): ThreatMatchRuleCreateProps => ({ description: 'Detecting root and admin users', enabled, index: ['auditbeat-*'], @@ -105,7 +107,7 @@ export const getCreateThreatMatchRulesSchemaMock = ( export const getCreateMachineLearningRulesSchemaMock = ( ruleId = 'rule-1' -): MachineLearningCreateSchema => ({ +): MachineLearningRuleCreateProps => ({ description: 'Detecting root and admin users', name: 'Query with a rule id', severity: 'high', @@ -116,7 +118,7 @@ export const getCreateMachineLearningRulesSchemaMock = ( machine_learning_job_id: 'typical-ml-job-id', }); -export const getCreateThresholdRulesSchemaMock = (ruleId = 'rule-1'): ThresholdCreateSchema => ({ +export const getCreateThresholdRulesSchemaMock = (ruleId = 'rule-1'): ThresholdRuleCreateProps => ({ description: 'Detecting root and admin users', name: 'Query with a rule id', severity: 'high', @@ -133,7 +135,7 @@ export const getCreateThresholdRulesSchemaMock = (ruleId = 'rule-1'): ThresholdC export const getCreateNewTermsRulesSchemaMock = ( ruleId = 'rule-1', enabled = false -): NewTermsCreateSchema => ({ +): NewTermsRuleCreateProps => ({ description: 'Detecting root and admin users', enabled, index: ['auditbeat-*'], @@ -152,7 +154,7 @@ export const getCreateNewTermsRulesSchemaMock = ( export const getUpdateRulesSchemaMock = ( id = '04128c15-0d1b-4716-a4c5-46997ac7f3bd' -): QueryUpdateSchema => ({ +): QueryRuleUpdateProps => ({ description: 'Detecting root and admin users', name: 'Query with a rule id', query: 'user.name: root or user.name: admin', @@ -165,7 +167,7 @@ export const getUpdateRulesSchemaMock = ( export const getUpdateMachineLearningSchemaMock = ( id = '04128c15-0d1b-4716-a4c5-46997ac7f3bd' -): MachineLearningUpdateSchema => ({ +): MachineLearningRuleUpdateProps => ({ description: 'Detecting root and admin users', name: 'Query with a rule id', severity: 'high', @@ -178,7 +180,7 @@ export const getUpdateMachineLearningSchemaMock = ( export const getUpdateNewTermsSchemaMock = ( id = '04128c15-0d1b-4716-a4c5-46997ac7f3bd' -): NewTermsUpdateSchema => ({ +): NewTermsRuleUpdateProps => ({ description: 'Detecting root and admin users', index: ['auditbeat-*'], name: 'Query with a rule id', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_request_schema.test.ts similarity index 86% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_request_schema.test.ts index 12a0c08582a0f..dab249557cc5a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_request_schema.test.ts @@ -6,11 +6,13 @@ */ import * as t from 'io-ts'; -import type { CreateRulesSchema, SavedQueryCreateSchema } from './rule_schemas'; -import { createRulesSchema, responseSchema } from './rule_schemas'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { getListArrayMock } from '../../schemas/types/lists.mock'; +import type { SavedQueryRuleCreateProps } from './rule_schemas'; +import { RuleCreateProps } from './rule_schemas'; import { getCreateSavedQueryRulesSchemaMock, getCreateThreatMatchRulesSchemaMock, @@ -18,14 +20,14 @@ import { getCreateThresholdRulesSchemaMock, getCreateRulesSchemaMockWithDataView, getCreateMachineLearningRulesSchemaMock, -} from './rule_schemas.mock'; -import { getListArrayMock } from '../types/lists.mock'; +} from './rule_request_schema.mock'; +import { buildResponseRuleSchema } from './build_rule_schemas'; describe('rules schema', () => { test('empty objects do not validate', () => { const payload = {}; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(message.errors.length).toBeGreaterThan(0); @@ -33,12 +35,12 @@ describe('rules schema', () => { }); test('made up values do not validate', () => { - const payload: CreateRulesSchema & { madeUp: string } = { + const payload: RuleCreateProps & { madeUp: string } = { ...getCreateRulesSchemaMock(), madeUp: 'hi', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); @@ -46,11 +48,11 @@ describe('rules schema', () => { }); test('[rule_id] does not validate', () => { - const payload: Partial<CreateRulesSchema> = { + const payload: Partial<RuleCreateProps> = { rule_id: 'rule-1', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(message.errors.length).toBeGreaterThan(0); @@ -58,12 +60,12 @@ describe('rules schema', () => { }); test('[rule_id, description] does not validate', () => { - const payload: Partial<CreateRulesSchema> = { + const payload: Partial<RuleCreateProps> = { rule_id: 'rule-1', description: 'some description', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(message.errors.length).toBeGreaterThan(0); @@ -71,13 +73,13 @@ describe('rules schema', () => { }); test('[rule_id, description, from] does not validate', () => { - const payload: Partial<CreateRulesSchema> = { + const payload: Partial<RuleCreateProps> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(message.errors.length).toBeGreaterThan(0); @@ -85,14 +87,14 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to] does not validate', () => { - const payload: Partial<CreateRulesSchema> = { + const payload: Partial<RuleCreateProps> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', to: 'now', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(message.errors.length).toBeGreaterThan(0); @@ -100,7 +102,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, name] does not validate', () => { - const payload: Partial<CreateRulesSchema> = { + const payload: Partial<RuleCreateProps> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -108,7 +110,7 @@ describe('rules schema', () => { name: 'some-name', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(message.errors.length).toBeGreaterThan(0); @@ -116,7 +118,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, name, severity] does not validate', () => { - const payload: Partial<CreateRulesSchema> = { + const payload: Partial<RuleCreateProps> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -125,7 +127,7 @@ describe('rules schema', () => { severity: 'low', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(message.errors.length).toBeGreaterThan(0); @@ -133,7 +135,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type] does not validate', () => { - const payload: Partial<CreateRulesSchema> = { + const payload: Partial<RuleCreateProps> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -143,7 +145,7 @@ describe('rules schema', () => { type: 'query', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -153,7 +155,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { - const payload: Partial<CreateRulesSchema> = { + const payload: Partial<RuleCreateProps> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -164,7 +166,7 @@ describe('rules schema', () => { type: 'query', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -174,7 +176,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { - const payload: Partial<CreateRulesSchema> = { + const payload: Partial<RuleCreateProps> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -186,7 +188,7 @@ describe('rules schema', () => { index: ['index-1'], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -196,7 +198,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -210,7 +212,7 @@ describe('rules schema', () => { interval: '5m', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -218,7 +220,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { - const payload: Partial<CreateRulesSchema> = { + const payload: Partial<RuleCreateProps> = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -232,7 +234,7 @@ describe('rules schema', () => { language: 'kuery', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -242,7 +244,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -257,7 +259,7 @@ describe('rules schema', () => { language: 'kuery', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -265,7 +267,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -281,7 +283,7 @@ describe('rules schema', () => { language: 'kuery', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -289,7 +291,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -302,7 +304,7 @@ describe('rules schema', () => { risk_score: 50, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -310,7 +312,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { author: [], severity_mapping: [], risk_score_mapping: [], @@ -327,7 +329,7 @@ describe('rules schema', () => { type: 'query', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -335,12 +337,12 @@ describe('rules schema', () => { }); test('You can send in a namespace', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), namespace: 'a namespace', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -348,12 +350,12 @@ describe('rules schema', () => { }); test('You can send in an empty array to threat', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), threat: [], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -361,7 +363,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threat] does validate', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -392,7 +394,7 @@ describe('rules schema', () => { ], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -400,12 +402,12 @@ describe('rules schema', () => { }); test('allows references to be sent as valid', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), references: ['index-1'], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -413,12 +415,12 @@ describe('rules schema', () => { }); test('references cannot be numbers', () => { - const payload: Omit<CreateRulesSchema, 'references'> & { references: number[] } = { + const payload: Omit<RuleCreateProps, 'references'> & { references: number[] } = { ...getCreateRulesSchemaMock(), references: [5], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "references"']); @@ -426,12 +428,12 @@ describe('rules schema', () => { }); test('indexes cannot be numbers', () => { - const payload: Omit<CreateRulesSchema, 'index'> & { index: number[] } = { + const payload: Omit<RuleCreateProps, 'index'> & { index: number[] } = { ...getCreateRulesSchemaMock(), index: [5], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "index"']); @@ -439,12 +441,12 @@ describe('rules schema', () => { }); test('saved_query type can have filters with it', () => { - const payload: SavedQueryCreateSchema = { + const payload: SavedQueryRuleCreateProps = { ...getCreateSavedQueryRulesSchemaMock(), filters: [], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -457,7 +459,7 @@ describe('rules schema', () => { filters: 'some string', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -467,12 +469,12 @@ describe('rules schema', () => { }); test('language validates with kuery', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), language: 'kuery', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -480,12 +482,12 @@ describe('rules schema', () => { }); test('language validates with lucene', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), language: 'lucene', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -498,7 +500,7 @@ describe('rules schema', () => { language: 'something-made-up', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -508,12 +510,12 @@ describe('rules schema', () => { }); test('max_signals cannot be negative', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), max_signals: -1, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -523,12 +525,12 @@ describe('rules schema', () => { }); test('max_signals cannot be zero', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), max_signals: 0, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "0" supplied to "max_signals"']); @@ -536,12 +538,12 @@ describe('rules schema', () => { }); test('max_signals can be 1', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), max_signals: 1, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -549,12 +551,12 @@ describe('rules schema', () => { }); test('You can optionally send in an array of tags', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), tags: ['tag_1', 'tag_2'], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -567,7 +569,7 @@ describe('rules schema', () => { tags: [0, 1, 2], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -599,7 +601,7 @@ describe('rules schema', () => { ], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -625,7 +627,7 @@ describe('rules schema', () => { ], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -649,7 +651,7 @@ describe('rules schema', () => { ], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -657,12 +659,12 @@ describe('rules schema', () => { }); test('You can optionally send in an array of false positives', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), false_positives: ['false_1', 'false_2'], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -675,7 +677,7 @@ describe('rules schema', () => { false_positives: [5, 4], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -691,7 +693,7 @@ describe('rules schema', () => { immutable: 5, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['invalid keys "immutable"']); @@ -699,12 +701,12 @@ describe('rules schema', () => { }); test('You cannot set the risk_score to 101', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), risk_score: 101, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -714,12 +716,12 @@ describe('rules schema', () => { }); test('You cannot set the risk_score to -1', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), risk_score: -1, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "-1" supplied to "risk_score"']); @@ -727,12 +729,12 @@ describe('rules schema', () => { }); test('You can set the risk_score to 0', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), risk_score: 0, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -740,12 +742,12 @@ describe('rules schema', () => { }); test('You can set the risk_score to 100', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), risk_score: 100, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -753,14 +755,14 @@ describe('rules schema', () => { }); test('You can set meta to any object you want', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), meta: { somethingMadeUp: { somethingElse: true }, }, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -773,7 +775,7 @@ describe('rules schema', () => { meta: 'should not work', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -784,12 +786,12 @@ describe('rules schema', () => { test('You can omit the query string when filters are present', () => { const { query, ...noQuery } = getCreateRulesSchemaMock(); - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...noQuery, filters: [], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -797,13 +799,13 @@ describe('rules schema', () => { }); test('validates with timeline_id and timeline_title', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), timeline_id: 'timeline-id', timeline_title: 'timeline-title', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -816,7 +818,7 @@ describe('rules schema', () => { severity: 'junk', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "junk" supplied to "severity"']); @@ -829,7 +831,7 @@ describe('rules schema', () => { actions: [{ id: 'id', action_type_id: 'action_type_id', params: {} }], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -844,7 +846,7 @@ describe('rules schema', () => { actions: [{ group: 'group', action_type_id: 'action_type_id', params: {} }], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -859,7 +861,7 @@ describe('rules schema', () => { actions: [{ group: 'group', id: 'id', params: {} }], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -874,7 +876,7 @@ describe('rules schema', () => { actions: [{ group: 'group', id: 'id', action_type_id: 'action_type_id' }], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -896,7 +898,7 @@ describe('rules schema', () => { ], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -907,12 +909,12 @@ describe('rules schema', () => { describe('note', () => { test('You can set note to a string', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), note: '# documentation markdown here', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -920,12 +922,12 @@ describe('rules schema', () => { }); test('You can set note to an empty string', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), note: '', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -940,7 +942,7 @@ describe('rules schema', () => { }, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -950,12 +952,12 @@ describe('rules schema', () => { }); test('empty name is not valid', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), name: '', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "name"']); @@ -963,12 +965,12 @@ describe('rules schema', () => { }); test('empty description is not valid', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { ...getCreateRulesSchemaMock(), description: '', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -978,7 +980,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note] does validate', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -992,7 +994,7 @@ describe('rules schema', () => { note: '# some markdown', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1001,7 +1003,7 @@ describe('rules schema', () => { }); test('machine_learning type does validate', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { type: 'machine_learning', anomaly_threshold: 50, machine_learning_job_id: 'linux_anomalous_network_activity_ecs', @@ -1023,7 +1025,7 @@ describe('rules schema', () => { rule_id: 'rule-1', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1033,7 +1035,7 @@ describe('rules schema', () => { test('saved_id is required when type is saved_query and will not validate without it', () => { /* eslint-disable @typescript-eslint/naming-convention */ const { saved_id, ...payload } = getCreateSavedQueryRulesSchemaMock(); - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1044,7 +1046,7 @@ describe('rules schema', () => { test('threshold is required when type is threshold and will not validate without it', () => { const { threshold, ...payload } = getCreateThresholdRulesSchemaMock(); - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1056,7 +1058,7 @@ describe('rules schema', () => { test('threshold rules fail validation if threshold is not greater than 0', () => { const payload = getCreateThresholdRulesSchemaMock(); payload.threshold.value = 0; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1067,7 +1069,7 @@ describe('rules schema', () => { describe('exception_list', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and exceptions_list] does validate', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1083,7 +1085,7 @@ describe('rules schema', () => { exceptions_list: getListArrayMock(), }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1091,7 +1093,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, and empty exceptions_list] does validate', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1107,7 +1109,7 @@ describe('rules schema', () => { exceptions_list: [], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1131,7 +1133,7 @@ describe('rules schema', () => { exceptions_list: [{ id: 'uuid_here', namespace_type: 'not a namespace type' }], }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1143,7 +1145,7 @@ describe('rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and non-existent exceptions_list] does validate with empty exceptions_list', () => { - const payload: CreateRulesSchema = { + const payload: RuleCreateProps = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1158,7 +1160,7 @@ describe('rules schema', () => { note: '# some markdown', }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1169,7 +1171,7 @@ describe('rules schema', () => { describe('threat_match', () => { test('You can set a threat query, index, mapping, filters when creating a rule', () => { const payload = getCreateThreatMatchRulesSchemaMock(); - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1180,7 +1182,7 @@ describe('rules schema', () => { /* eslint-disable @typescript-eslint/naming-convention */ const { threat_index, threat_query, threat_mapping, ...payload } = getCreateThreatMatchRulesSchemaMock(); - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1194,7 +1196,7 @@ describe('rules schema', () => { test('fails validation when threat_mapping is an empty array', () => { const payload = getCreateThreatMatchRulesSchemaMock(); payload.threat_mapping = []; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1207,7 +1209,7 @@ describe('rules schema', () => { describe('data_view_id', () => { test('validates when "data_view_id" and index are defined', () => { const payload = { ...getCreateRulesSchemaMockWithDataView(), index: ['auditbeat-*'] }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); @@ -1215,12 +1217,12 @@ describe('rules schema', () => { }); test('"data_view_id" cannot be a number', () => { - const payload: Omit<CreateRulesSchema, 'data_view_id'> & { data_view_id: number } = { + const payload: Omit<RuleCreateProps, 'data_view_id'> & { data_view_id: number } = { ...getCreateRulesSchemaMockWithDataView(), data_view_id: 5, }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([ @@ -1232,7 +1234,7 @@ describe('rules schema', () => { test('it should validate a type of "query" with "data_view_id" defined', () => { const payload = getCreateRulesSchemaMockWithDataView(); - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = getCreateRulesSchemaMockWithDataView(); @@ -1244,7 +1246,7 @@ describe('rules schema', () => { test('it should validate a type of "saved_query" with "data_view_id" defined', () => { const payload = { ...getCreateSavedQueryRulesSchemaMock(), data_view_id: 'logs-*' }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getCreateSavedQueryRulesSchemaMock(), data_view_id: 'logs-*' }; @@ -1256,7 +1258,7 @@ describe('rules schema', () => { test('it should validate a type of "threat_match" with "data_view_id" defined', () => { const payload = { ...getCreateThreatMatchRulesSchemaMock(), data_view_id: 'logs-*' }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getCreateThreatMatchRulesSchemaMock(), data_view_id: 'logs-*' }; @@ -1268,7 +1270,7 @@ describe('rules schema', () => { test('it should validate a type of "threshold" with "data_view_id" defined', () => { const payload = { ...getCreateThresholdRulesSchemaMock(), data_view_id: 'logs-*' }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getCreateThresholdRulesSchemaMock(), data_view_id: 'logs-*' }; @@ -1280,7 +1282,7 @@ describe('rules schema', () => { test('it should NOT validate a type of "machine_learning" with "data_view_id" defined', () => { const payload = { ...getCreateMachineLearningRulesSchemaMock(), data_view_id: 'logs-*' }; - const decoded = createRulesSchema.decode(payload); + const decoded = RuleCreateProps.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -1301,7 +1303,11 @@ describe('rules schema', () => { testDefaultableString: t.string, }, }; - const schema = responseSchema(testSchema.required, testSchema.optional, testSchema.defaultable); + const schema = buildResponseRuleSchema( + testSchema.required, + testSchema.optional, + testSchema.defaultable + ); describe('required fields', () => { test('should allow required fields with the correct type', () => { diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_response_schema.mock.ts similarity index 87% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_response_schema.mock.ts index 189cbd1045d67..0a99da6b4f6f3 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_response_schema.mock.ts @@ -7,18 +7,18 @@ import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../constants'; import type { - EqlResponseSchema, - MachineLearningResponseSchema, - QueryResponseSchema, - SavedQueryResponseSchema, - SharedResponseSchema, - ThreatMatchResponseSchema, -} from '../request'; -import { getListArrayMock } from '../types/lists.mock'; + EqlRule, + MachineLearningRule, + QueryRule, + SavedQueryRule, + SharedResponseProps, + ThreatMatchRule, +} from './rule_schemas'; +import { getListArrayMock } from '../../schemas/types/lists.mock'; export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; -const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE): SharedResponseSchema => ({ +const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE): SharedResponseProps => ({ author: [], id: '7a7065d7-6e8b-4aae-8d20-c93613dec9f9', created_at: new Date(anchorDate).toISOString(), @@ -65,7 +65,7 @@ const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE): SharedResponse namespace: undefined, }); -export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): QueryResponseSchema => ({ +export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): QueryRule => ({ ...getResponseBaseParams(anchorDate), query: 'user.name: root or user.name: admin', type: 'query', @@ -76,9 +76,8 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): QueryRespo saved_id: undefined, response_actions: undefined, }); -export const getSavedQuerySchemaMock = ( - anchorDate: string = ANCHOR_DATE -): SavedQueryResponseSchema => ({ + +export const getSavedQuerySchemaMock = (anchorDate: string = ANCHOR_DATE): SavedQueryRule => ({ ...getResponseBaseParams(anchorDate), query: 'user.name: root or user.name: admin', type: 'saved_query', @@ -90,9 +89,7 @@ export const getSavedQuerySchemaMock = ( response_actions: undefined, }); -export const getRulesMlSchemaMock = ( - anchorDate: string = ANCHOR_DATE -): MachineLearningResponseSchema => { +export const getRulesMlSchemaMock = (anchorDate: string = ANCHOR_DATE): MachineLearningRule => { return { ...getResponseBaseParams(anchorDate), type: 'machine_learning', @@ -101,9 +98,7 @@ export const getRulesMlSchemaMock = ( }; }; -export const getThreatMatchingSchemaMock = ( - anchorDate: string = ANCHOR_DATE -): ThreatMatchResponseSchema => { +export const getThreatMatchingSchemaMock = (anchorDate: string = ANCHOR_DATE): ThreatMatchRule => { return { ...getResponseBaseParams(anchorDate), type: 'threat_match', @@ -145,9 +140,7 @@ export const getThreatMatchingSchemaMock = ( * Useful for e2e backend tests where it doesn't have date time and other * server side properties attached to it. */ -export const getThreatMatchingSchemaPartialMock = ( - enabled = false -): Partial<ThreatMatchResponseSchema> => { +export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial<ThreatMatchRule> => { return { author: [], created_by: 'elastic', @@ -216,7 +209,7 @@ export const getThreatMatchingSchemaPartialMock = ( }; }; -export const getRulesEqlSchemaMock = (anchorDate: string = ANCHOR_DATE): EqlResponseSchema => { +export const getRulesEqlSchemaMock = (anchorDate: string = ANCHOR_DATE): EqlRule => { return { ...getResponseBaseParams(anchorDate), language: 'eql', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_response_schema.test.ts similarity index 80% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts rename to x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_response_schema.test.ts index 0a337eb28bc1c..0032ee60267c4 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_response_schema.test.ts @@ -7,23 +7,22 @@ import { left } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; - import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; + +import { RuleResponse } from './rule_schemas'; import { getRulesSchemaMock, getRulesMlSchemaMock, getSavedQuerySchemaMock, getThreatMatchingSchemaMock, getRulesEqlSchemaMock, -} from './rules_schema.mocks'; -import { fullResponseSchema } from '../request'; -import type { FullResponseSchema } from '../request'; +} from './rule_response_schema.mock'; -describe('rules_schema', () => { +describe('Rule response schema', () => { test('it should validate a type of "query" without anything extra', () => { const payload = getRulesSchemaMock(); - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = getRulesSchemaMock(); @@ -33,10 +32,10 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "query" when it has extra data', () => { - const payload: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const payload: RuleResponse & { invalid_extra_data?: string } = getRulesSchemaMock(); payload.invalid_extra_data = 'invalid_extra_data'; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -45,10 +44,10 @@ describe('rules_schema', () => { }); test('it should NOT validate invalid_data for the type', () => { - const payload: Omit<FullResponseSchema, 'type'> & { type: string } = getRulesSchemaMock(); + const payload: Omit<RuleResponse, 'type'> & { type: string } = getRulesSchemaMock(); payload.type = 'invalid_data'; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -57,11 +56,11 @@ describe('rules_schema', () => { }); test('it should validate a type of "query" with a saved_id together', () => { - const payload: FullResponseSchema & { saved_id?: string } = getRulesSchemaMock(); + const payload: RuleResponse & { saved_id?: string } = getRulesSchemaMock(); payload.type = 'query'; payload.saved_id = 'save id 123'; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -72,7 +71,7 @@ describe('rules_schema', () => { test('it should validate a type of "saved_query" with a "saved_id" dependent', () => { const payload = getSavedQuerySchemaMock(); - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = getSavedQuerySchemaMock(); @@ -82,11 +81,11 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "saved_query" without a "saved_id" dependent', () => { - const payload: FullResponseSchema & { saved_id?: string } = getSavedQuerySchemaMock(); + const payload: RuleResponse & { saved_id?: string } = getSavedQuerySchemaMock(); // @ts-expect-error delete payload.saved_id; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -97,11 +96,11 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "saved_query" when it has extra data', () => { - const payload: FullResponseSchema & { saved_id?: string; invalid_extra_data?: string } = + const payload: RuleResponse & { saved_id?: string; invalid_extra_data?: string } = getSavedQuerySchemaMock(); payload.invalid_extra_data = 'invalid_extra_data'; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -114,7 +113,7 @@ describe('rules_schema', () => { payload.timeline_id = 'some timeline id'; payload.timeline_title = 'some timeline title'; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = getRulesSchemaMock(); @@ -126,12 +125,12 @@ describe('rules_schema', () => { }); test('it should NOT validate a type of "timeline_id" if there is "timeline_title" dependent when it has extra invalid data', () => { - const payload: FullResponseSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); + const payload: RuleResponse & { invalid_extra_data?: string } = getRulesSchemaMock(); payload.timeline_id = 'some timeline id'; payload.timeline_title = 'some timeline title'; payload.invalid_extra_data = 'invalid_extra_data'; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -143,7 +142,7 @@ describe('rules_schema', () => { test('it should validate an empty array for "exceptions_list"', () => { const payload = getRulesSchemaMock(); payload.exceptions_list = []; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = getRulesSchemaMock(); @@ -153,11 +152,11 @@ describe('rules_schema', () => { }); test('it should NOT validate when "exceptions_list" is not expected type', () => { - const payload: Omit<FullResponseSchema, 'exceptions_list'> & { + const payload: Omit<RuleResponse, 'exceptions_list'> & { exceptions_list?: string; } = { ...getRulesSchemaMock(), exceptions_list: 'invalid_data' }; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); @@ -172,7 +171,7 @@ describe('rules_schema', () => { test('it should validate a type of "query" with "data_view_id" defined', () => { const payload = { ...getRulesSchemaMock(), data_view_id: 'logs-*' }; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getRulesSchemaMock(), data_view_id: 'logs-*' }; @@ -182,14 +181,14 @@ describe('rules_schema', () => { }); test('it should validate a type of "saved_query" with "data_view_id" defined', () => { - const payload: FullResponseSchema & { saved_id?: string; data_view_id?: string } = + const payload: RuleResponse & { saved_id?: string; data_view_id?: string } = getSavedQuerySchemaMock(); payload.data_view_id = 'logs-*'; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected: FullResponseSchema & { saved_id?: string; data_view_id?: string } = + const expected: RuleResponse & { saved_id?: string; data_view_id?: string } = getSavedQuerySchemaMock(); expected.data_view_id = 'logs-*'; @@ -201,7 +200,7 @@ describe('rules_schema', () => { test('it should validate a type of "eql" with "data_view_id" defined', () => { const payload = { ...getRulesEqlSchemaMock(), data_view_id: 'logs-*' }; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getRulesEqlSchemaMock(), data_view_id: 'logs-*' }; @@ -213,7 +212,7 @@ describe('rules_schema', () => { test('it should validate a type of "threat_match" with "data_view_id" defined', () => { const payload = { ...getThreatMatchingSchemaMock(), data_view_id: 'logs-*' }; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); const expected = { ...getThreatMatchingSchemaMock(), data_view_id: 'logs-*' }; @@ -225,7 +224,7 @@ describe('rules_schema', () => { test('it should NOT validate a type of "machine_learning" with "data_view_id" defined', () => { const payload = { ...getRulesMlSchemaMock(), data_view_id: 'logs-*' }; - const decoded = fullResponseSchema.decode(payload); + const decoded = RuleResponse.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_schemas.ts new file mode 100644 index 0000000000000..9985ef4102736 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/rule_schemas.ts @@ -0,0 +1,554 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; + +import { + concurrent_searches, + items_per_search, + machine_learning_job_id, + RiskScore, + RiskScoreMapping, + RuleActionArray, + RuleActionThrottle, + RuleInterval, + RuleIntervalFrom, + RuleIntervalTo, + Severity, + SeverityMapping, + threat_filters, + threat_index, + threat_indicator_path, + threat_mapping, + threat_query, +} from '@kbn/securitysolution-io-ts-alerting-types'; + +import { RuleExecutionSummary } from '../../rule_monitoring'; +import { ResponseActionArray } from '../../rule_response_actions/schemas'; +import { + saved_id, + anomaly_threshold, + updated_at, + updated_by, + created_at, + created_by, +} from '../../schemas/common'; + +import { + AlertsIndex, + AlertsIndexNamespace, + BuildingBlockType, + DataViewId, + ExceptionListArray, + IndexPatternArray, + InvestigationGuide, + IsRuleEnabled, + IsRuleImmutable, + MaxSignals, + RuleAuthorArray, + RuleDescription, + RuleFalsePositiveArray, + RuleFilterArray, + RuleLicense, + RuleMetadata, + RuleName, + RuleObjectId, + RuleQuery, + RuleReferenceArray, + RuleSignatureId, + RuleTagArray, + RuleVersion, + SetupGuide, + ThreatArray, +} from './common_attributes/misc_attributes'; +import { + RuleNameOverride, + TimestampOverride, + TimestampOverrideFallbackDisabled, +} from './common_attributes/field_overrides'; +import { + SavedObjectResolveAliasPurpose, + SavedObjectResolveAliasTargetId, + SavedObjectResolveOutcome, +} from './common_attributes/saved_objects'; +import { RelatedIntegrationArray } from './common_attributes/related_integrations'; +import { RequiredFieldArray } from './common_attributes/required_fields'; +import { TimelineTemplateId, TimelineTemplateTitle } from './common_attributes/timeline_template'; +import { + EventCategoryOverride, + TiebreakerField, + TimestampField, +} from './specific_attributes/eql_attributes'; +import { Threshold } from './specific_attributes/threshold_attributes'; +import { HistoryWindowStart, NewTermsFields } from './specific_attributes/new_terms_attributes'; + +import { buildRuleSchemas } from './build_rule_schemas'; + +// ------------------------------------------------------------------------------------------------- +// Base schema + +const baseSchema = buildRuleSchemas({ + required: { + name: RuleName, + description: RuleDescription, + risk_score: RiskScore, + severity: Severity, + }, + optional: { + // Field overrides + rule_name_override: RuleNameOverride, + timestamp_override: TimestampOverride, + timestamp_override_fallback_disabled: TimestampOverrideFallbackDisabled, + // Timeline template + timeline_id: TimelineTemplateId, + timeline_title: TimelineTemplateTitle, + // Atributes related to SavedObjectsClient.resolve API + outcome: SavedObjectResolveOutcome, + alias_target_id: SavedObjectResolveAliasTargetId, + alias_purpose: SavedObjectResolveAliasPurpose, + // Misc attributes + license: RuleLicense, + note: InvestigationGuide, + building_block_type: BuildingBlockType, + output_index: AlertsIndex, + namespace: AlertsIndexNamespace, + meta: RuleMetadata, + }, + defaultable: { + // Main attributes + version: RuleVersion, + tags: RuleTagArray, + enabled: IsRuleEnabled, + // Field overrides + risk_score_mapping: RiskScoreMapping, + severity_mapping: SeverityMapping, + // Rule schedule + interval: RuleInterval, + from: RuleIntervalFrom, + to: RuleIntervalTo, + // Rule actions + actions: RuleActionArray, + throttle: RuleActionThrottle, + // Rule exceptions + exceptions_list: ExceptionListArray, + // Misc attributes + author: RuleAuthorArray, + false_positives: RuleFalsePositiveArray, + references: RuleReferenceArray, + // maxSignals not used in ML rules but probably should be used + max_signals: MaxSignals, + threat: ThreatArray, + }, +}); + +const responseRequiredFields = { + id: RuleObjectId, + rule_id: RuleSignatureId, + immutable: IsRuleImmutable, + updated_at, + updated_by, + created_at, + created_by, + + // NOTE: For now, Related Integrations, Required Fields and Setup Guide are supported for prebuilt + // rules only. We don't want to allow users to edit these 3 fields via the API. If we added them + // to baseParams.defaultable, they would become a part of the request schema as optional fields. + // This is why we add them here, in order to add them only to the response schema. + related_integrations: RelatedIntegrationArray, + required_fields: RequiredFieldArray, + setup: SetupGuide, +}; + +const responseOptionalFields = { + execution_summary: RuleExecutionSummary, +}; + +export type BaseCreateProps = t.TypeOf<typeof BaseCreateProps>; +export const BaseCreateProps = baseSchema.create; + +// ------------------------------------------------------------------------------------------------- +// Shared schemas + +// "Shared" types are the same across all rule types, and built from "baseSchema" above +// with some variations for each route. These intersect with type specific schemas below +// to create the full schema for each route. + +type SharedCreateProps = t.TypeOf<typeof SharedCreateProps>; +const SharedCreateProps = t.intersection([ + baseSchema.create, + t.exact(t.partial({ rule_id: RuleSignatureId })), +]); + +type SharedUpdateProps = t.TypeOf<typeof SharedUpdateProps>; +const SharedUpdateProps = t.intersection([ + baseSchema.create, + t.exact(t.partial({ rule_id: RuleSignatureId })), + t.exact(t.partial({ id: RuleObjectId })), +]); + +type SharedPatchProps = t.TypeOf<typeof SharedPatchProps>; +const SharedPatchProps = t.intersection([ + baseSchema.patch, + t.exact(t.partial({ rule_id: RuleSignatureId, id: RuleObjectId })), +]); + +export type SharedResponseProps = t.TypeOf<typeof SharedResponseProps>; +export const SharedResponseProps = t.intersection([ + baseSchema.response, + t.exact(t.type(responseRequiredFields)), + t.exact(t.partial(responseOptionalFields)), +]); + +// ------------------------------------------------------------------------------------------------- +// EQL rule schema + +const eqlSchema = buildRuleSchemas({ + required: { + type: t.literal('eql'), + language: t.literal('eql'), + query: RuleQuery, + }, + optional: { + index: IndexPatternArray, + data_view_id: DataViewId, + filters: RuleFilterArray, + timestamp_field: TimestampField, + event_category_override: EventCategoryOverride, + tiebreaker_field: TiebreakerField, + }, + defaultable: {}, +}); + +export type EqlRule = t.TypeOf<typeof EqlRule>; +export const EqlRule = t.intersection([SharedResponseProps, eqlSchema.response]); + +export type EqlRuleCreateProps = t.TypeOf<typeof EqlRuleCreateProps>; +export const EqlRuleCreateProps = t.intersection([SharedCreateProps, eqlSchema.create]); + +export type EqlRuleUpdateProps = t.TypeOf<typeof EqlRuleUpdateProps>; +export const EqlRuleUpdateProps = t.intersection([SharedUpdateProps, eqlSchema.create]); + +export type EqlRulePatchProps = t.TypeOf<typeof EqlRulePatchProps>; +export const EqlRulePatchProps = t.intersection([SharedPatchProps, eqlSchema.patch]); + +export type EqlPatchParams = t.TypeOf<typeof EqlPatchParams>; +export const EqlPatchParams = eqlSchema.patch; + +// ------------------------------------------------------------------------------------------------- +// Indicator Match rule schema + +const threatMatchSchema = buildRuleSchemas({ + required: { + type: t.literal('threat_match'), + query: RuleQuery, + threat_query, + threat_mapping, + threat_index, + }, + optional: { + index: IndexPatternArray, + data_view_id: DataViewId, + filters: RuleFilterArray, + saved_id, + threat_filters, + threat_indicator_path, + threat_language: t.keyof({ kuery: null, lucene: null }), + concurrent_searches, + items_per_search, + }, + defaultable: { + language: t.keyof({ kuery: null, lucene: null }), + }, +}); + +export type ThreatMatchRule = t.TypeOf<typeof ThreatMatchRule>; +export const ThreatMatchRule = t.intersection([SharedResponseProps, threatMatchSchema.response]); + +export type ThreatMatchRuleCreateProps = t.TypeOf<typeof ThreatMatchRuleCreateProps>; +export const ThreatMatchRuleCreateProps = t.intersection([ + SharedCreateProps, + threatMatchSchema.create, +]); + +export type ThreatMatchRuleUpdateProps = t.TypeOf<typeof ThreatMatchRuleUpdateProps>; +export const ThreatMatchRuleUpdateProps = t.intersection([ + SharedUpdateProps, + threatMatchSchema.create, +]); + +export type ThreatMatchRulePatchProps = t.TypeOf<typeof ThreatMatchRulePatchProps>; +export const ThreatMatchRulePatchProps = t.intersection([ + SharedPatchProps, + threatMatchSchema.patch, +]); + +export type ThreatMatchPatchParams = t.TypeOf<typeof ThreatMatchPatchParams>; +export const ThreatMatchPatchParams = threatMatchSchema.patch; + +// ------------------------------------------------------------------------------------------------- +// Custom Query rule schema + +const querySchema = buildRuleSchemas({ + required: { + type: t.literal('query'), + }, + optional: { + index: IndexPatternArray, + data_view_id: DataViewId, + filters: RuleFilterArray, + saved_id, + response_actions: ResponseActionArray, + }, + defaultable: { + query: RuleQuery, + language: t.keyof({ kuery: null, lucene: null }), + }, +}); + +export type QueryRule = t.TypeOf<typeof QueryRule>; +export const QueryRule = t.intersection([SharedResponseProps, querySchema.response]); + +export type QueryRuleCreateProps = t.TypeOf<typeof QueryRuleCreateProps>; +export const QueryRuleCreateProps = t.intersection([SharedCreateProps, querySchema.create]); + +export type QueryRuleUpdateProps = t.TypeOf<typeof QueryRuleUpdateProps>; +export const QueryRuleUpdateProps = t.intersection([SharedUpdateProps, querySchema.create]); + +export type QueryRulePatchProps = t.TypeOf<typeof QueryRulePatchProps>; +export const QueryRulePatchProps = t.intersection([SharedPatchProps, querySchema.patch]); + +export type QueryPatchParams = t.TypeOf<typeof QueryPatchParams>; +export const QueryPatchParams = querySchema.patch; + +// ------------------------------------------------------------------------------------------------- +// Saved Query rule schema + +const savedQuerySchema = buildRuleSchemas({ + required: { + type: t.literal('saved_query'), + saved_id, + }, + optional: { + // Having language, query, and filters possibly defined adds more code confusion and probably user confusion + // if the saved object gets deleted for some reason + index: IndexPatternArray, + data_view_id: DataViewId, + query: RuleQuery, + filters: RuleFilterArray, + response_actions: ResponseActionArray, + }, + defaultable: { + language: t.keyof({ kuery: null, lucene: null }), + }, +}); + +export type SavedQueryRule = t.TypeOf<typeof SavedQueryRule>; +export const SavedQueryRule = t.intersection([SharedResponseProps, savedQuerySchema.response]); + +export type SavedQueryRuleCreateProps = t.TypeOf<typeof SavedQueryRuleCreateProps>; +export const SavedQueryRuleCreateProps = t.intersection([ + SharedCreateProps, + savedQuerySchema.create, +]); + +export type SavedQueryRuleUpdateProps = t.TypeOf<typeof SavedQueryRuleUpdateProps>; +export const SavedQueryRuleUpdateProps = t.intersection([ + SharedUpdateProps, + savedQuerySchema.create, +]); + +export type SavedQueryRulePatchProps = t.TypeOf<typeof SavedQueryRulePatchProps>; +export const SavedQueryRulePatchProps = t.intersection([SharedPatchProps, savedQuerySchema.patch]); + +export type SavedQueryPatchParams = t.TypeOf<typeof SavedQueryPatchParams>; +export const SavedQueryPatchParams = savedQuerySchema.patch; + +// ------------------------------------------------------------------------------------------------- +// Threshold rule schema + +const thresholdSchema = buildRuleSchemas({ + required: { + type: t.literal('threshold'), + query: RuleQuery, + threshold: Threshold, + }, + optional: { + index: IndexPatternArray, + data_view_id: DataViewId, + filters: RuleFilterArray, + saved_id, + }, + defaultable: { + language: t.keyof({ kuery: null, lucene: null }), + }, +}); + +export type ThresholdRule = t.TypeOf<typeof ThresholdRule>; +export const ThresholdRule = t.intersection([SharedResponseProps, thresholdSchema.response]); + +export type ThresholdRuleCreateProps = t.TypeOf<typeof ThresholdRuleCreateProps>; +export const ThresholdRuleCreateProps = t.intersection([SharedCreateProps, thresholdSchema.create]); + +export type ThresholdRuleUpdateProps = t.TypeOf<typeof ThresholdRuleUpdateProps>; +export const ThresholdRuleUpdateProps = t.intersection([SharedUpdateProps, thresholdSchema.create]); + +export type ThresholdRulePatchProps = t.TypeOf<typeof ThresholdRulePatchProps>; +export const ThresholdRulePatchProps = t.intersection([SharedPatchProps, thresholdSchema.patch]); + +export type ThresholdPatchParams = t.TypeOf<typeof ThresholdPatchParams>; +export const ThresholdPatchParams = thresholdSchema.patch; + +// ------------------------------------------------------------------------------------------------- +// Machine Learning rule schema + +const machineLearningSchema = buildRuleSchemas({ + required: { + type: t.literal('machine_learning'), + anomaly_threshold, + machine_learning_job_id, + }, + optional: {}, + defaultable: {}, +}); + +export type MachineLearningRule = t.TypeOf<typeof MachineLearningRule>; +export const MachineLearningRule = t.intersection([ + SharedResponseProps, + machineLearningSchema.response, +]); + +export type MachineLearningRuleCreateProps = t.TypeOf<typeof MachineLearningRuleCreateProps>; +export const MachineLearningRuleCreateProps = t.intersection([ + SharedCreateProps, + machineLearningSchema.create, +]); + +export type MachineLearningRuleUpdateProps = t.TypeOf<typeof MachineLearningRuleUpdateProps>; +export const MachineLearningRuleUpdateProps = t.intersection([ + SharedUpdateProps, + machineLearningSchema.create, +]); + +export type MachineLearningRulePatchProps = t.TypeOf<typeof MachineLearningRulePatchProps>; +export const MachineLearningRulePatchProps = t.intersection([ + SharedPatchProps, + machineLearningSchema.patch, +]); + +export type MachineLearningPatchParams = t.TypeOf<typeof MachineLearningPatchParams>; +export const MachineLearningPatchParams = machineLearningSchema.patch; + +// ------------------------------------------------------------------------------------------------- +// New Terms rule schema + +const newTermsSchema = buildRuleSchemas({ + required: { + type: t.literal('new_terms'), + query: RuleQuery, + new_terms_fields: NewTermsFields, + history_window_start: HistoryWindowStart, + }, + optional: { + index: IndexPatternArray, + data_view_id: DataViewId, + filters: RuleFilterArray, + }, + defaultable: { + language: t.keyof({ kuery: null, lucene: null }), + }, +}); + +export type NewTermsRule = t.TypeOf<typeof NewTermsRule>; +export const NewTermsRule = t.intersection([SharedResponseProps, newTermsSchema.response]); + +export type NewTermsRuleCreateProps = t.TypeOf<typeof NewTermsRuleCreateProps>; +export const NewTermsRuleCreateProps = t.intersection([SharedCreateProps, newTermsSchema.create]); + +export type NewTermsRuleUpdateProps = t.TypeOf<typeof NewTermsRuleUpdateProps>; +export const NewTermsRuleUpdateProps = t.intersection([SharedUpdateProps, newTermsSchema.create]); + +export type NewTermsRulePatchProps = t.TypeOf<typeof NewTermsRulePatchProps>; +export const NewTermsRulePatchProps = t.intersection([SharedPatchProps, newTermsSchema.patch]); + +export type NewTermsPatchParams = t.TypeOf<typeof NewTermsPatchParams>; +export const NewTermsPatchParams = newTermsSchema.patch; + +// ------------------------------------------------------------------------------------------------- +// Combined type specific schemas + +export type TypeSpecificCreateProps = t.TypeOf<typeof TypeSpecificCreateProps>; +export const TypeSpecificCreateProps = t.union([ + eqlSchema.create, + threatMatchSchema.create, + querySchema.create, + savedQuerySchema.create, + thresholdSchema.create, + machineLearningSchema.create, + newTermsSchema.create, +]); + +export type TypeSpecificPatchProps = t.TypeOf<typeof TypeSpecificPatchProps>; +export const TypeSpecificPatchProps = t.union([ + eqlSchema.patch, + threatMatchSchema.patch, + querySchema.patch, + savedQuerySchema.patch, + thresholdSchema.patch, + machineLearningSchema.patch, + newTermsSchema.patch, +]); + +export type TypeSpecificResponse = t.TypeOf<typeof TypeSpecificResponse>; +export const TypeSpecificResponse = t.union([ + eqlSchema.response, + threatMatchSchema.response, + querySchema.response, + savedQuerySchema.response, + thresholdSchema.response, + machineLearningSchema.response, + newTermsSchema.response, +]); + +// ------------------------------------------------------------------------------------------------- +// Final combined schemas + +export type RuleCreateProps = t.TypeOf<typeof RuleCreateProps>; +export const RuleCreateProps = t.intersection([SharedCreateProps, TypeSpecificCreateProps]); + +export type RuleUpdateProps = t.TypeOf<typeof RuleUpdateProps>; +export const RuleUpdateProps = t.intersection([TypeSpecificCreateProps, SharedUpdateProps]); + +export type RulePatchProps = t.TypeOf<typeof RulePatchProps>; +export const RulePatchProps = t.intersection([TypeSpecificPatchProps, SharedPatchProps]); + +export type RuleResponse = t.TypeOf<typeof RuleResponse>; +export const RuleResponse = t.intersection([SharedResponseProps, TypeSpecificResponse]); + +// ------------------------------------------------------------------------------------------------- +// Rule preview schemas + +// TODO: Move to the rule_preview subdomain + +export type PreviewRulesSchema = t.TypeOf<typeof previewRulesSchema>; +export const previewRulesSchema = t.intersection([ + SharedCreateProps, + TypeSpecificCreateProps, + t.type({ invocationCount: t.number, timeframeEnd: t.string }), +]); + +export interface RulePreviewLogs { + errors: string[]; + warnings: string[]; + startedAt?: string; + duration: number; +} + +export interface PreviewResponse { + previewId: string | undefined; + logs: RulePreviewLogs[] | undefined; + isAborted: boolean | undefined; +} diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/eql_attributes.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/eql_attributes.ts new file mode 100644 index 0000000000000..0bc029fa0d4a5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/eql_attributes.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 * as t from 'io-ts'; + +// Attributes specific to EQL rules + +export type EventCategoryOverride = t.TypeOf<typeof EventCategoryOverride>; +export const EventCategoryOverride = t.string; // should be non-empty string? + +export type TimestampField = t.TypeOf<typeof TimestampField>; +export const TimestampField = t.string; // should be non-empty string? + +export type TiebreakerField = t.TypeOf<typeof TiebreakerField>; +export const TiebreakerField = t.string; // should be non-empty string? diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/new_terms_attributes.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/new_terms_attributes.ts new file mode 100644 index 0000000000000..15bf73ba150e5 --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/new_terms_attributes.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. + */ + +import * as t from 'io-ts'; +import { LimitedSizeArray, NonEmptyString } from '@kbn/securitysolution-io-ts-types'; + +// Attributes specific to New Terms rules + +/** + * New terms rule type currently only supports a single term, but should support more in the future + */ +export type NewTermsFields = t.TypeOf<typeof NewTermsFields>; +export const NewTermsFields = LimitedSizeArray({ codec: t.string, minSize: 1, maxSize: 1 }); + +export type HistoryWindowStart = t.TypeOf<typeof HistoryWindowStart>; +export const HistoryWindowStart = NonEmptyString; diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/threshold_attributes.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/threshold_attributes.ts new file mode 100644 index 0000000000000..eb5639c97ab3e --- /dev/null +++ b/x-pack/plugins/security_solution/common/detection_engine/rule_schema/model/specific_attributes/threshold_attributes.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as t from 'io-ts'; +import { PositiveInteger, PositiveIntegerGreaterThanZero } from '@kbn/securitysolution-io-ts-types'; + +// Attributes specific to Threshold rules + +const thresholdField = t.exact( + t.type({ + field: t.union([t.string, t.array(t.string)]), // Covers pre- and post-7.12 + value: PositiveIntegerGreaterThanZero, + }) +); + +const thresholdFieldNormalized = t.exact( + t.type({ + field: t.array(t.string), + value: PositiveIntegerGreaterThanZero, + }) +); + +const thresholdCardinalityField = t.exact( + t.type({ + field: t.string, + value: PositiveInteger, + }) +); + +export type Threshold = t.TypeOf<typeof Threshold>; +export const Threshold = t.intersection([ + thresholdField, + t.exact( + t.partial({ + cardinality: t.array(thresholdCardinalityField), + }) + ), +]); + +export type ThresholdNormalized = t.TypeOf<typeof ThresholdNormalized>; +export const ThresholdNormalized = t.intersection([ + thresholdFieldNormalized, + t.exact( + t.partial({ + cardinality: t.array(thresholdCardinalityField), + }) + ), +]); + +export type ThresholdWithCardinality = t.TypeOf<typeof ThresholdWithCardinality>; +export const ThresholdWithCardinality = t.intersection([ + thresholdFieldNormalized, + t.exact( + t.type({ + cardinality: t.array(thresholdCardinalityField), + }) + ), +]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/index.ts index ad8745a8caf21..e129a72362ed7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/index.ts @@ -5,8 +5,6 @@ * 2.0. */ -export * from './installed_integrations'; export * from './pagination'; -export * from './rule_params'; export * from './schemas'; export * from './sorting'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index f7c5fe6307736..52ba9e06622d4 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -7,55 +7,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { - IsoDateString, - NonEmptyString, - PositiveInteger, - PositiveIntegerGreaterThanZero, - UUID, - LimitedSizeArray, -} from '@kbn/securitysolution-io-ts-types'; import * as t from 'io-ts'; - -export const author = t.array(t.string); -export type Author = t.TypeOf<typeof author>; - -export const building_block_type = t.string; -export type BuildingBlockType = t.TypeOf<typeof building_block_type>; - -export const buildingBlockTypeOrUndefined = t.union([building_block_type, t.undefined]); - -export const description = NonEmptyString; -export type Description = t.TypeOf<typeof description>; - -// outcome is a property of the saved object resolve api -// will tell us info about the rule after 8.0 migrations -export const outcome = t.union([ - t.literal('exactMatch'), - t.literal('aliasMatch'), - t.literal('conflict'), -]); -export type Outcome = t.TypeOf<typeof outcome>; - -export const alias_target_id = t.string; -export const alias_purpose = t.union([ - t.literal('savedObjectConversion'), - t.literal('savedObjectImport'), -]); -export const enabled = t.boolean; -export type Enabled = t.TypeOf<typeof enabled>; -export const event_category_override = t.string; -export const eventCategoryOverrideOrUndefined = t.union([event_category_override, t.undefined]); - -export const tiebreaker_field = t.string; - -export const tiebreakerFieldOrUndefined = t.union([tiebreaker_field, t.undefined]); - -export const timestamp_field = t.string; - -export const timestampFieldOrUndefined = t.union([timestamp_field, t.undefined]); - -export const false_positives = t.array(t.string); +import { IsoDateString, PositiveInteger } from '@kbn/securitysolution-io-ts-types'; export const file_name = t.string; export type FileName = t.TypeOf<typeof file_name>; @@ -63,112 +16,13 @@ export type FileName = t.TypeOf<typeof file_name>; export const exclude_export_details = t.boolean; export type ExcludeExportDetails = t.TypeOf<typeof exclude_export_details>; -export const namespace = t.string; -export type Namespace = t.TypeOf<typeof namespace>; - -/** - * TODO: Right now the filters is an "unknown", when it could more than likely - * become the actual ESFilter as a type. - */ -export const filters = t.array(t.unknown); // Filters are not easily type-able yet -export type Filters = t.TypeOf<typeof filters>; // Filters are not easily type-able yet - -export const filtersOrUndefined = t.union([filters, t.undefined]); -export type FiltersOrUndefined = t.TypeOf<typeof filtersOrUndefined>; - -export const immutable = t.boolean; -export type Immutable = t.TypeOf<typeof immutable>; - -// Note: Never make this a strict uuid, we allow the rule_id to be any string at the moment -// in case we encounter 3rd party rule systems which might be using auto incrementing numbers -// or other different things. -export const rule_id = t.string; -export type RuleId = t.TypeOf<typeof rule_id>; - -export const ruleIdOrUndefined = t.union([rule_id, t.undefined]); -export type RuleIdOrUndefined = t.TypeOf<typeof ruleIdOrUndefined>; - -export const id = UUID; -export type Id = t.TypeOf<typeof id>; - -export const idOrUndefined = t.union([id, t.undefined]); -export type IdOrUndefined = t.TypeOf<typeof idOrUndefined>; - -export const index = t.array(t.string); -export type Index = t.TypeOf<typeof index>; - -export const data_view_id = t.string; - -export const dataViewIdOrUndefined = t.union([data_view_id, t.undefined]); - -export const indexOrUndefined = t.union([index, t.undefined]); -export type IndexOrUndefined = t.TypeOf<typeof indexOrUndefined>; - -export const interval = t.string; -export type Interval = t.TypeOf<typeof interval>; - -export const query = t.string; -export type Query = t.TypeOf<typeof query>; - -export const queryOrUndefined = t.union([query, t.undefined]); -export type QueryOrUndefined = t.TypeOf<typeof queryOrUndefined>; - -export const license = t.string; -export type License = t.TypeOf<typeof license>; - -export const licenseOrUndefined = t.union([license, t.undefined]); - -export const objects = t.array(t.type({ rule_id })); - -export const output_index = t.string; - export const saved_id = t.string; export const savedIdOrUndefined = t.union([saved_id, t.undefined]); export type SavedIdOrUndefined = t.TypeOf<typeof savedIdOrUndefined>; -export const timeline_id = t.string; -export type TimelineId = t.TypeOf<typeof timeline_id>; - -export const timelineIdOrUndefined = t.union([timeline_id, t.undefined]); - -export const timeline_title = t.string; - -export const timelineTitleOrUndefined = t.union([timeline_title, t.undefined]); - -export const timestamp_override = t.string; -export type TimestampOverride = t.TypeOf<typeof timestamp_override>; - -export const timestampOverrideOrUndefined = t.union([timestamp_override, t.undefined]); -export type TimestampOverrideOrUndefined = t.TypeOf<typeof timestampOverrideOrUndefined>; - export const anomaly_threshold = PositiveInteger; -export const timestamp_override_fallback_disabled = t.boolean; - -export const timestampOverrideFallbackDisabledOrUndefined = t.union([ - timestamp_override_fallback_disabled, - t.undefined, -]); - -/** - * Note that this is a non-exact io-ts type as we allow extra meta information - * to be added to the meta object - */ -export const meta = t.object; -export type Meta = t.TypeOf<typeof meta>; -export const metaOrUndefined = t.union([meta, t.undefined]); -export type MetaOrUndefined = t.TypeOf<typeof metaOrUndefined>; - -export const name = NonEmptyString; -export type Name = t.TypeOf<typeof name>; - -export const rule_name_override = t.string; -export type RuleNameOverride = t.TypeOf<typeof rule_name_override>; - -export const ruleNameOverrideOrUndefined = t.union([rule_name_override, t.undefined]); -export type RuleNameOverrideOrUndefined = t.TypeOf<typeof ruleNameOverrideOrUndefined>; - export const status = t.keyof({ open: null, closed: null, @@ -179,122 +33,34 @@ export type Status = t.TypeOf<typeof status>; export const conflicts = t.keyof({ abort: null, proceed: null }); -// TODO: Create a regular expression type or custom date math part type here -export const to = t.string; -export type To = t.TypeOf<typeof to>; - export const queryFilter = t.string; export type QueryFilter = t.TypeOf<typeof queryFilter>; export const queryFilterOrUndefined = t.union([queryFilter, t.undefined]); export type QueryFilterOrUndefined = t.TypeOf<typeof queryFilterOrUndefined>; -export const references = t.array(t.string); -export type References = t.TypeOf<typeof references>; - export const signal_ids = t.array(t.string); export type SignalIds = t.TypeOf<typeof signal_ids>; // TODO: Can this be more strict or is this is the set of all Elastic Queries? export const signal_status_query = t.object; -export const tags = t.array(t.string); -export type Tags = t.TypeOf<typeof tags>; - export const fields = t.array(t.string); export type Fields = t.TypeOf<typeof fields>; export const fieldsOrUndefined = t.union([fields, t.undefined]); export type FieldsOrUndefined = t.TypeOf<typeof fieldsOrUndefined>; -export const thresholdField = t.exact( - t.type({ - field: t.union([t.string, t.array(t.string)]), // Covers pre- and post-7.12 - value: PositiveIntegerGreaterThanZero, - }) -); - -export const thresholdFieldNormalized = t.exact( - t.type({ - field: t.array(t.string), - value: PositiveIntegerGreaterThanZero, - }) -); - -export const thresholdCardinalityField = t.exact( - t.type({ - field: t.string, - value: PositiveInteger, - }) -); - -export const threshold = t.intersection([ - thresholdField, - t.exact( - t.partial({ - cardinality: t.array(thresholdCardinalityField), - }) - ), -]); -export type Threshold = t.TypeOf<typeof threshold>; - -export const thresholdNormalized = t.intersection([ - thresholdFieldNormalized, - t.exact( - t.partial({ - cardinality: t.array(thresholdCardinalityField), - }) - ), -]); -export type ThresholdNormalized = t.TypeOf<typeof thresholdNormalized>; - -export const thresholdWithCardinality = t.intersection([ - thresholdFieldNormalized, - t.exact( - t.type({ - cardinality: t.array(thresholdCardinalityField), - }) - ), -]); -export type ThresholdWithCardinality = t.TypeOf<typeof thresholdWithCardinality>; - -// New terms rule type currently only supports a single term, but should support more in the future -export const newTermsFields = LimitedSizeArray({ codec: t.string, minSize: 1, maxSize: 1 }); -export type NewTermsFields = t.TypeOf<typeof newTermsFields>; - -export const historyWindowStart = NonEmptyString; -export type HistoryWindowStart = t.TypeOf<typeof historyWindowStart>; - export const created_at = IsoDateString; - export const updated_at = IsoDateString; - -export const updated_by = t.string; - export const created_by = t.string; +export const updated_by = t.string; -export const rules_installed = PositiveInteger; -export const rules_updated = PositiveInteger; export const status_code = PositiveInteger; export const message = t.string; export const perPage = PositiveInteger; export const total = PositiveInteger; export const success = t.boolean; export const success_count = PositiveInteger; -export const rules_custom_installed = PositiveInteger; -export const rules_not_installed = PositiveInteger; -export const rules_not_updated = PositiveInteger; - -export const timelines_installed = PositiveInteger; -export const timelines_updated = PositiveInteger; -export const timelines_not_installed = PositiveInteger; -export const timelines_not_updated = PositiveInteger; - -export const note = t.string; -export type Note = t.TypeOf<typeof note>; - -export const namespaceOrUndefined = t.union([namespace, t.undefined]); - -export const noteOrUndefined = t.union([note, t.undefined]); export const indexRecord = t.record( t.string, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rule_exception_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rule_exception_schema.ts deleted file mode 100644 index e2d23d21abd53..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rule_exception_schema.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 * as t from 'io-ts'; - -import type { CreateRuleExceptionListItemSchemaDecoded } from '@kbn/securitysolution-io-ts-list-types'; -import { createRuleExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -import type { RequiredKeepUndefined } from '@kbn/osquery-plugin/common/types'; - -export const createRuleExceptionsSchema = t.exact( - t.type({ - items: t.array(createRuleExceptionListItemSchema), - }) -); - -export type CreateRuleExceptionSchema = t.TypeOf<typeof createRuleExceptionsSchema>; - -// This type is used after a decode since some things are defaults after a decode. -export type CreateRuleExceptionSchemaDecoded = Omit< - RequiredKeepUndefined<t.TypeOf<typeof createRuleExceptionsSchema>>, - 'items' -> & { - items: CreateRuleExceptionListItemSchemaDecoded[]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts deleted file mode 100644 index c429656d575f8..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_rules_type_dependents.ts +++ /dev/null @@ -1,76 +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 { CreateRulesSchema } from './rule_schemas'; - -export const validateTimelineId = (rule: CreateRulesSchema): string[] => { - if (rule.timeline_id != null) { - if (rule.timeline_title == null) { - return ['when "timeline_id" exists, "timeline_title" must also exist']; - } else if (rule.timeline_id === '') { - return ['"timeline_id" cannot be an empty string']; - } else { - return []; - } - } - return []; -}; - -export const validateTimelineTitle = (rule: CreateRulesSchema): string[] => { - if (rule.timeline_title != null) { - if (rule.timeline_id == null) { - return ['when "timeline_title" exists, "timeline_id" must also exist']; - } else if (rule.timeline_title === '') { - return ['"timeline_title" cannot be an empty string']; - } else { - return []; - } - } - return []; -}; - -export const validateThreatMapping = (rule: CreateRulesSchema): string[] => { - const errors: string[] = []; - if (rule.type === 'threat_match') { - if (rule.concurrent_searches != null && rule.items_per_search == null) { - errors.push('when "concurrent_searches" exists, "items_per_search" must also exist'); - } - if (rule.concurrent_searches == null && rule.items_per_search != null) { - errors.push('when "items_per_search" exists, "concurrent_searches" must also exist'); - } - } - return errors; -}; - -export const validateThreshold = (rule: CreateRulesSchema): string[] => { - const errors: string[] = []; - if (rule.type === 'threshold') { - if (!rule.threshold) { - errors.push('when "type" is "threshold", "threshold" is required'); - } else { - if ( - rule.threshold.cardinality?.length && - rule.threshold.field.includes(rule.threshold.cardinality[0].field) - ) { - errors.push('Cardinality of a field that is being aggregated on is always 1'); - } - if (Array.isArray(rule.threshold.field) && rule.threshold.field.length > 3) { - errors.push('Number of fields must be 3 or less'); - } - } - } - return errors; -}; - -export const createRuleValidateTypeDependents = (rule: CreateRulesSchema): string[] => { - return [ - ...validateTimelineId(rule), - ...validateTimelineTitle(rule), - ...validateThreatMapping(rule), - ...validateThreshold(rule), - ]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_signals_migration_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_signals_migration_schema.ts index 55267c27ee37f..1eba4855bf0d3 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_signals_migration_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/create_signals_migration_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import { PositiveInteger, PositiveIntegerGreaterThanZero } from '@kbn/securitysolution-io-ts-types'; -import { index } from '../common/schemas'; +import { IndexPatternArray } from '../../rule_schema'; export const signalsReindexOptions = t.partial({ requests_per_second: t.number, @@ -21,7 +21,7 @@ export type SignalsReindexOptions = t.TypeOf<typeof signalsReindexOptions>; export const createSignalsMigrationSchema = t.intersection([ t.exact( t.type({ - index, + index: IndexPatternArray, }) ), t.exact(signalsReindexOptions), diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/export_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/export_rules_schema.ts deleted file mode 100644 index 9541d37c78049..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/export_rules_schema.ts +++ /dev/null @@ -1,32 +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 * as t from 'io-ts'; - -import { DefaultExportFileName } from '@kbn/securitysolution-io-ts-alerting-types'; -import { DefaultStringBooleanFalse } from '@kbn/securitysolution-io-ts-types'; -import type { FileName, ExcludeExportDetails } from '../common/schemas'; -import { rule_id } from '../common/schemas'; - -const objects = t.array(t.exact(t.type({ rule_id }))); -export const exportRulesSchema = t.union([t.exact(t.type({ objects })), t.null]); -export type ExportRulesSchema = t.TypeOf<typeof exportRulesSchema>; -export type ExportRulesSchemaDecoded = ExportRulesSchema; - -export const exportRulesQuerySchema = t.exact( - t.partial({ file_name: DefaultExportFileName, exclude_export_details: DefaultStringBooleanFalse }) -); - -export type ExportRulesQuerySchema = t.TypeOf<typeof exportRulesQuerySchema>; - -export type ExportRulesQuerySchemaDecoded = Omit< - ExportRulesQuerySchema, - 'file_name' | 'exclude_export_details' -> & { - file_name: FileName; - exclude_export_details: ExcludeExportDetails; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rule_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rule_type_dependents.test.ts deleted file mode 100644 index 50afe7f970acb..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rule_type_dependents.test.ts +++ /dev/null @@ -1,46 +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 { FindRulesSchema } from './find_rules_schema'; -import { findRuleValidateTypeDependents } from './find_rules_type_dependents'; - -describe('find_rules_type_dependents', () => { - test('You can have an empty sort_field and empty sort_order', () => { - const schema: FindRulesSchema = {}; - const errors = findRuleValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - - test('You can have both a sort_field and and a sort_order', () => { - const schema: FindRulesSchema = { - sort_field: 'some field', - sort_order: 'asc', - }; - const errors = findRuleValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - - test('You cannot have sort_field without sort_order', () => { - const schema: FindRulesSchema = { - sort_field: 'some field', - }; - const errors = findRuleValidateTypeDependents(schema); - expect(errors).toEqual([ - 'when "sort_order" and "sort_field" must exist together or not at all', - ]); - }); - - test('You cannot have sort_order without sort_field', () => { - const schema: FindRulesSchema = { - sort_order: 'asc', - }; - const errors = findRuleValidateTypeDependents(schema); - expect(errors).toEqual([ - 'when "sort_order" and "sort_field" must exist together or not at all', - ]); - }); -}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rules_type_dependents.ts deleted file mode 100644 index f9bd6dc56f104..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/find_rules_type_dependents.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. - */ - -import type { FindRulesSchema } from './find_rules_schema'; - -export const validateSortOrder = (find: FindRulesSchema): string[] => { - if (find.sort_order != null || find.sort_field != null) { - if (find.sort_order == null || find.sort_field == null) { - return ['when "sort_order" and "sort_field" must exist together or not at all']; - } else { - return []; - } - } else { - return []; - } -}; - -export const findRuleValidateTypeDependents = (schema: FindRulesSchema): string[] => { - return [...validateSortOrder(schema)]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/get_signals_migration_status_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/get_signals_migration_status_schema.ts index c0969768d8be6..f2a9fc210df2b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/get_signals_migration_status_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/get_signals_migration_status_schema.ts @@ -7,11 +7,11 @@ import * as t from 'io-ts'; -import { from } from '@kbn/securitysolution-io-ts-alerting-types'; +import { RuleIntervalFrom } from '@kbn/securitysolution-io-ts-alerting-types'; export const getSignalsMigrationStatusSchema = t.exact( t.type({ - from, + from: RuleIntervalFrom, }) ); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.test.ts deleted file mode 100644 index cd7ec37a85edb..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.test.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 { getImportRulesSchemaMock } from './import_rules_schema.mock'; -import type { ImportRulesSchema } from './import_rules_schema'; -import { importRuleValidateTypeDependents } from './import_rules_type_dependents'; - -describe('import_rules_type_dependents', () => { - test('You cannot omit timeline_title when timeline_id is present', () => { - const schema: ImportRulesSchema = { - ...getImportRulesSchemaMock(), - timeline_id: '123', - }; - delete schema.timeline_title; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); - }); - - test('You cannot have empty string for timeline_title when timeline_id is present', () => { - const schema: ImportRulesSchema = { - ...getImportRulesSchemaMock(), - timeline_id: '123', - timeline_title: '', - }; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"timeline_title" cannot be an empty string']); - }); - - test('You cannot have timeline_title with an empty timeline_id', () => { - const schema: ImportRulesSchema = { - ...getImportRulesSchemaMock(), - timeline_id: '', - timeline_title: 'some-title', - }; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"timeline_id" cannot be an empty string']); - }); - - test('You cannot have timeline_title without timeline_id', () => { - const schema: ImportRulesSchema = { - ...getImportRulesSchemaMock(), - timeline_title: 'some-title', - }; - delete schema.timeline_id; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); - }); -}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts index 6e77066299249..56ea598c58b0d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/index.ts @@ -5,18 +5,5 @@ * 2.0. */ -export * from './add_prepackaged_rules_schema'; -export * from './create_rules_bulk_schema'; -export * from './create_rule_exception_schema'; -export * from './export_rules_schema'; -export * from './find_rules_schema'; -export * from './import_rules_schema'; -export * from './patch_rules_bulk_schema'; -export * from './patch_rules_schema'; -export * from './perform_bulk_action_schema'; -export * from './query_rule_by_id_schema'; -export * from './query_rules_schema'; export * from './query_signals_index_schema'; -export * from './rule_schemas'; export * from './set_signal_status_schema'; -export * from './update_rules_bulk_schema'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts deleted file mode 100644 index 5f4f5a4b16891..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts +++ /dev/null @@ -1,19 +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 * as t from 'io-ts'; - -import { patchTypeSpecific, sharedPatchSchema, thresholdPatchParams } from './rule_schemas'; - -/** - * All of the patch elements should default to undefined if not set - */ -export const patchRulesSchema = t.intersection([patchTypeSpecific, sharedPatchSchema]); -export type PatchRulesSchema = t.TypeOf<typeof patchRulesSchema>; - -const thresholdPatchSchema = t.intersection([thresholdPatchParams, sharedPatchSchema]); -export type ThresholdPatchSchema = t.TypeOf<typeof thresholdPatchSchema>; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.test.ts deleted file mode 100644 index abb64ec5522f2..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.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 { - getPatchRulesSchemaMock, - getPatchThresholdRulesSchemaMock, -} from './patch_rules_schema.mock'; -import type { PatchRulesSchema, ThresholdPatchSchema } from './patch_rules_schema'; -import { patchRuleValidateTypeDependents } from './patch_rules_type_dependents'; - -describe('patch_rules_type_dependents', () => { - test('You cannot omit timeline_title when timeline_id is present', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - timeline_id: '123', - }; - delete schema.timeline_title; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "timeline_id" exists, "timeline_title" must also exist']); - }); - - test('You cannot have empty string for timeline_title when timeline_id is present', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - timeline_id: '123', - timeline_title: '', - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"timeline_title" cannot be an empty string']); - }); - - test('You cannot have timeline_title with an empty timeline_id', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - timeline_id: '', - timeline_title: 'some-title', - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"timeline_id" cannot be an empty string']); - }); - - test('You cannot have timeline_title without timeline_id', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - timeline_title: 'some-title', - }; - delete schema.timeline_id; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); - }); - - test('You cannot have both an id and a rule_id', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - id: 'some-id', - rule_id: 'some-rule-id', - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['both "id" and "rule_id" cannot exist, choose one or the other']); - }); - - test('You must set either an id or a rule_id', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - }; - delete schema.id; - delete schema.rule_id; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['either "id" or "rule_id" must be set']); - }); - - test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => { - const schema: ThresholdPatchSchema = { - ...getPatchThresholdRulesSchemaMock(), - threshold: { - field: '', - value: -1, - }, - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['"threshold.value" has to be bigger than 0']); - }); - - test('threshold.field should contain 3 items or less', () => { - const schema: ThresholdPatchSchema = { - ...getPatchThresholdRulesSchemaMock(), - threshold: { - field: ['field-1', 'field-2', 'field-3', 'field-4'], - value: 1, - }, - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['Number of fields must be 3 or less']); - }); - - test('threshold.cardinality[0].field should not be in threshold.field', () => { - const schema: ThresholdPatchSchema = { - ...getPatchThresholdRulesSchemaMock(), - threshold: { - field: ['field-1', 'field-2', 'field-3'], - value: 1, - cardinality: [ - { - field: 'field-1', - value: 2, - }, - ], - }, - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['Cardinality of a field that is being aggregated on is always 1']); - }); -}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rule_by_id_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rule_by_id_schema.test.ts deleted file mode 100644 index 7266bebc8027d..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rule_by_id_schema.test.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 { QueryRuleByIdSchema } from './query_rule_by_id_schema'; -import { queryRuleByIdSchema } from './query_rule_by_id_schema'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -describe('query_rule_by_id_schema', () => { - test('empty objects do not validate', () => { - const payload: Partial<QueryRuleByIdSchema> = {}; - - const decoded = queryRuleByIdSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "undefined" supplied to "id"']); - expect(message.schema).toEqual({}); - }); - - test('validates string for id', () => { - const payload: Partial<QueryRuleByIdSchema> = { - id: '4656dc92-5832-11ea-8e2d-0242ac130003', - }; - - const decoded = queryRuleByIdSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - id: '4656dc92-5832-11ea-8e2d-0242ac130003', - }); - }); -}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_bulk_schema.ts deleted file mode 100644 index afa485687043f..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/query_rules_bulk_schema.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. - */ - -import * as t from 'io-ts'; - -import type { QueryRulesSchemaDecoded } from './query_rules_schema'; -import { queryRulesSchema } from './query_rules_schema'; - -export const queryRulesBulkSchema = t.array(queryRulesSchema); -export type QueryRulesBulkSchema = t.TypeOf<typeof queryRulesBulkSchema>; - -export type QueryRulesBulkSchemaDecoded = QueryRulesSchemaDecoded[]; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts deleted file mode 100644 index 61e28f1edb902..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts +++ /dev/null @@ -1,541 +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 * as t from 'io-ts'; - -import { - actions, - from, - risk_score, - machine_learning_job_id, - risk_score_mapping, - threat_filters, - threat_query, - threat_mapping, - threat_index, - threat_indicator_path, - concurrent_searches, - items_per_search, - threats, - severity_mapping, - severity, - max_signals, - throttle, -} from '@kbn/securitysolution-io-ts-alerting-types'; -import { listArray } from '@kbn/securitysolution-io-ts-list-types'; -import { version } from '@kbn/securitysolution-io-ts-types'; - -import { RuleExecutionSummary } from '../../rule_monitoring'; -import { - id, - index, - data_view_id, - filters, - timestamp_field, - event_category_override, - tiebreaker_field, - building_block_type, - note, - license, - timeline_id, - timeline_title, - meta, - rule_name_override, - timestamp_override, - timestamp_override_fallback_disabled, - author, - description, - false_positives, - rule_id, - immutable, - output_index, - query, - to, - references, - saved_id, - threshold, - anomaly_threshold, - name, - tags, - interval, - enabled, - outcome, - alias_target_id, - alias_purpose, - updated_at, - updated_by, - created_at, - created_by, - namespace, - RelatedIntegrationArray, - RequiredFieldArray, - SetupGuide, - newTermsFields, - historyWindowStart, -} from '../common'; -import { ResponseActionArray } from '../../rule_response_actions/schemas'; - -export const createSchema = < - Required extends t.Props, - Optional extends t.Props, - Defaultable extends t.Props ->( - requiredFields: Required, - optionalFields: Optional, - defaultableFields: Defaultable -) => { - return t.intersection([ - t.exact(t.type(requiredFields)), - t.exact(t.partial(optionalFields)), - t.exact(t.partial(defaultableFields)), - ]); -}; - -const patchSchema = < - Required extends t.Props, - Optional extends t.Props, - Defaultable extends t.Props ->( - requiredFields: Required, - optionalFields: Optional, - defaultableFields: Defaultable -) => { - return t.intersection([ - t.partial(requiredFields), - t.partial(optionalFields), - t.partial(defaultableFields), - ]); -}; - -type OrUndefined<P extends t.Props> = { - [K in keyof P]: P[K] | t.UndefinedC; -}; - -export const responseSchema = < - Required extends t.Props, - Optional extends t.Props, - Defaultable extends t.Props ->( - requiredFields: Required, - optionalFields: Optional, - defaultableFields: Defaultable -) => { - // This bit of logic is to force all fields to be accounted for in conversions from the internal - // rule schema to the response schema. Rather than use `t.partial`, which makes each field optional, - // we make each field required but possibly undefined. The result is that if a field is forgotten in - // the conversion from internal schema to response schema TS will report an error. If we just used t.partial - // instead, then optional fields can be accidentally omitted from the conversion - and any actual values - // in those fields internally will be stripped in the response. - const optionalWithUndefined = Object.keys(optionalFields).reduce<t.Props>((acc, key) => { - acc[key] = t.union([optionalFields[key], t.undefined]); - return acc; - }, {}) as OrUndefined<Optional>; - return t.intersection([ - t.exact(t.type(requiredFields)), - t.exact(t.type(optionalWithUndefined)), - t.exact(t.type(defaultableFields)), - ]); -}; - -export const buildAPISchemas = <R extends t.Props, O extends t.Props, D extends t.Props>( - params: APIParams<R, O, D> -) => { - return { - create: createSchema(params.required, params.optional, params.defaultable), - patch: patchSchema(params.required, params.optional, params.defaultable), - response: responseSchema(params.required, params.optional, params.defaultable), - }; -}; - -interface APIParams< - Required extends t.Props, - Optional extends t.Props, - Defaultable extends t.Props -> { - required: Required; - optional: Optional; - defaultable: Defaultable; -} - -const baseParams = { - required: { - name, - description, - risk_score, - severity, - }, - optional: { - building_block_type, - note, - license, - outcome, - alias_target_id, - alias_purpose, - output_index, - timeline_id, - timeline_title, - meta, - rule_name_override, - timestamp_override, - timestamp_override_fallback_disabled, - namespace, - }, - defaultable: { - tags, - interval, - enabled, - throttle, - actions, - author, - false_positives, - from, - // maxSignals not used in ML rules but probably should be used - max_signals, - risk_score_mapping, - severity_mapping, - threat: threats, - to, - references, - version, - exceptions_list: listArray, - }, -}; -const { - create: baseCreateParams, - patch: basePatchParams, - response: baseResponseParams, -} = buildAPISchemas(baseParams); -export { baseCreateParams }; - -// "shared" types are the same across all rule types, and built from "baseParams" above -// with some variations for each route. These intersect with type specific schemas below -// to create the full schema for each route. -export const sharedCreateSchema = t.intersection([ - baseCreateParams, - t.exact(t.partial({ rule_id })), -]); -export type SharedCreateSchema = t.TypeOf<typeof sharedCreateSchema>; - -export const sharedUpdateSchema = t.intersection([ - baseCreateParams, - t.exact(t.partial({ rule_id })), - t.exact(t.partial({ id })), -]); -export type SharedUpdateSchema = t.TypeOf<typeof sharedUpdateSchema>; - -export const sharedPatchSchema = t.intersection([ - basePatchParams, - t.exact(t.partial({ rule_id, id })), -]); - -// START type specific parameter definitions -// ----------------------------------------- -const eqlRuleParams = { - required: { - type: t.literal('eql'), - language: t.literal('eql'), - query, - }, - optional: { - index, - data_view_id, - filters, - timestamp_field, - event_category_override, - tiebreaker_field, - }, - defaultable: {}, -}; -const { - create: eqlCreateParams, - patch: eqlPatchParams, - response: eqlResponseParams, -} = buildAPISchemas(eqlRuleParams); -export { eqlCreateParams, eqlResponseParams }; - -const threatMatchRuleParams = { - required: { - type: t.literal('threat_match'), - query, - threat_query, - threat_mapping, - threat_index, - }, - optional: { - index, - data_view_id, - filters, - saved_id, - threat_filters, - threat_indicator_path, - threat_language: t.keyof({ kuery: null, lucene: null }), - concurrent_searches, - items_per_search, - }, - defaultable: { - language: t.keyof({ kuery: null, lucene: null }), - }, -}; -const { - create: threatMatchCreateParams, - patch: threatMatchPatchParams, - response: threatMatchResponseParams, -} = buildAPISchemas(threatMatchRuleParams); -export { threatMatchCreateParams, threatMatchResponseParams }; - -const queryRuleParams = { - required: { - type: t.literal('query'), - }, - optional: { - index, - data_view_id, - filters, - saved_id, - response_actions: ResponseActionArray, - }, - defaultable: { - query, - language: t.keyof({ kuery: null, lucene: null }), - }, -}; -const { - create: queryCreateParams, - patch: queryPatchParams, - response: queryResponseParams, -} = buildAPISchemas(queryRuleParams); - -export { queryCreateParams, queryResponseParams }; - -const savedQueryRuleParams = { - required: { - type: t.literal('saved_query'), - saved_id, - }, - optional: { - // Having language, query, and filters possibly defined adds more code confusion and probably user confusion - // if the saved object gets deleted for some reason - index, - data_view_id, - query, - filters, - response_actions: ResponseActionArray, - }, - defaultable: { - language: t.keyof({ kuery: null, lucene: null }), - }, -}; -const { - create: savedQueryCreateParams, - patch: savedQueryPatchParams, - response: savedQueryResponseParams, -} = buildAPISchemas(savedQueryRuleParams); - -export { savedQueryCreateParams, savedQueryResponseParams }; - -const thresholdRuleParams = { - required: { - type: t.literal('threshold'), - query, - threshold, - }, - optional: { - index, - data_view_id, - filters, - saved_id, - }, - defaultable: { - language: t.keyof({ kuery: null, lucene: null }), - }, -}; -const { - create: thresholdCreateParams, - patch: thresholdPatchParams, - response: thresholdResponseParams, -} = buildAPISchemas(thresholdRuleParams); - -export { thresholdCreateParams, thresholdResponseParams }; - -const machineLearningRuleParams = { - required: { - type: t.literal('machine_learning'), - anomaly_threshold, - machine_learning_job_id, - }, - optional: {}, - defaultable: {}, -}; -const { - create: machineLearningCreateParams, - patch: machineLearningPatchParams, - response: machineLearningResponseParams, -} = buildAPISchemas(machineLearningRuleParams); - -export { machineLearningCreateParams, machineLearningResponseParams }; - -const newTermsRuleParams = { - required: { - type: t.literal('new_terms'), - query, - new_terms_fields: newTermsFields, - history_window_start: historyWindowStart, - }, - optional: { - index, - data_view_id, - filters, - }, - defaultable: { - language: t.keyof({ kuery: null, lucene: null }), - }, -}; -const { - create: newTermsCreateParams, - patch: newTermsPatchParams, - response: newTermsResponseParams, -} = buildAPISchemas(newTermsRuleParams); - -export { newTermsCreateParams, newTermsResponseParams }; -// --------------------------------------- -// END type specific parameter definitions - -export const createTypeSpecific = t.union([ - eqlCreateParams, - threatMatchCreateParams, - queryCreateParams, - savedQueryCreateParams, - thresholdCreateParams, - machineLearningCreateParams, - newTermsCreateParams, -]); -export type CreateTypeSpecific = t.TypeOf<typeof createTypeSpecific>; - -// Convenience types for building specific types of rules -type CreateSchema<T> = SharedCreateSchema & T; -export type EqlCreateSchema = CreateSchema<t.TypeOf<typeof eqlCreateParams>>; -export type ThreatMatchCreateSchema = CreateSchema<t.TypeOf<typeof threatMatchCreateParams>>; -export type QueryCreateSchema = CreateSchema<t.TypeOf<typeof queryCreateParams>>; -export type SavedQueryCreateSchema = CreateSchema<t.TypeOf<typeof savedQueryCreateParams>>; -export type ThresholdCreateSchema = CreateSchema<t.TypeOf<typeof thresholdCreateParams>>; -export type MachineLearningCreateSchema = CreateSchema< - t.TypeOf<typeof machineLearningCreateParams> ->; -export type NewTermsCreateSchema = CreateSchema<t.TypeOf<typeof newTermsCreateParams>>; - -export const createRulesSchema = t.intersection([sharedCreateSchema, createTypeSpecific]); -export type CreateRulesSchema = t.TypeOf<typeof createRulesSchema>; -export const previewRulesSchema = t.intersection([ - sharedCreateSchema, - createTypeSpecific, - t.type({ invocationCount: t.number, timeframeEnd: t.string }), -]); -export type PreviewRulesSchema = t.TypeOf<typeof previewRulesSchema>; - -type UpdateSchema<T> = SharedUpdateSchema & T; -export type QueryUpdateSchema = UpdateSchema<t.TypeOf<typeof queryCreateParams>>; -export type MachineLearningUpdateSchema = UpdateSchema< - t.TypeOf<typeof machineLearningCreateParams> ->; -export type NewTermsUpdateSchema = UpdateSchema<t.TypeOf<typeof newTermsCreateParams>>; - -export const patchTypeSpecific = t.union([ - eqlPatchParams, - threatMatchPatchParams, - queryPatchParams, - savedQueryPatchParams, - thresholdPatchParams, - machineLearningPatchParams, - newTermsPatchParams, -]); -export { - eqlPatchParams, - threatMatchPatchParams, - queryPatchParams, - savedQueryPatchParams, - thresholdPatchParams, - machineLearningPatchParams, - newTermsPatchParams, -}; - -export type EqlPatchParams = t.TypeOf<typeof eqlPatchParams>; -export type ThreatMatchPatchParams = t.TypeOf<typeof threatMatchPatchParams>; -export type QueryPatchParams = t.TypeOf<typeof queryPatchParams>; -export type SavedQueryPatchParams = t.TypeOf<typeof savedQueryPatchParams>; -export type ThresholdPatchParams = t.TypeOf<typeof thresholdPatchParams>; -export type MachineLearningPatchParams = t.TypeOf<typeof machineLearningPatchParams>; -export type NewTermsPatchParams = t.TypeOf<typeof newTermsPatchParams>; - -const responseTypeSpecific = t.union([ - eqlResponseParams, - threatMatchResponseParams, - queryResponseParams, - savedQueryResponseParams, - thresholdResponseParams, - machineLearningResponseParams, - newTermsResponseParams, -]); -export type ResponseTypeSpecific = t.TypeOf<typeof responseTypeSpecific>; - -export const updateRulesSchema = t.intersection([createTypeSpecific, sharedUpdateSchema]); -export type UpdateRulesSchema = t.TypeOf<typeof updateRulesSchema>; - -const responseRequiredFields = { - id, - rule_id, - immutable, - updated_at, - updated_by, - created_at, - created_by, - - // NOTE: For now, Related Integrations, Required Fields and Setup Guide are supported for prebuilt - // rules only. We don't want to allow users to edit these 3 fields via the API. If we added them - // to baseParams.defaultable, they would become a part of the request schema as optional fields. - // This is why we add them here, in order to add them only to the response schema. - related_integrations: RelatedIntegrationArray, - required_fields: RequiredFieldArray, - setup: SetupGuide, -}; - -const responseOptionalFields = { - execution_summary: RuleExecutionSummary, -}; - -const sharedResponseSchema = t.intersection([ - baseResponseParams, - t.exact(t.type(responseRequiredFields)), - t.exact(t.partial(responseOptionalFields)), -]); -export type SharedResponseSchema = t.TypeOf<typeof sharedResponseSchema>; -export const fullResponseSchema = t.intersection([sharedResponseSchema, responseTypeSpecific]); -export type FullResponseSchema = t.TypeOf<typeof fullResponseSchema>; - -// Convenience types for type specific responses -type ResponseSchema<T> = SharedResponseSchema & T; -export type EqlResponseSchema = ResponseSchema<t.TypeOf<typeof eqlResponseParams>>; -export type ThreatMatchResponseSchema = ResponseSchema<t.TypeOf<typeof threatMatchResponseParams>>; -export type QueryResponseSchema = ResponseSchema<t.TypeOf<typeof queryResponseParams>>; -export type SavedQueryResponseSchema = ResponseSchema<t.TypeOf<typeof savedQueryResponseParams>>; -export type ThresholdResponseSchema = ResponseSchema<t.TypeOf<typeof thresholdResponseParams>>; -export type MachineLearningResponseSchema = ResponseSchema< - t.TypeOf<typeof machineLearningResponseParams> ->; -export type NewTermsResponseSchema = ResponseSchema<t.TypeOf<typeof newTermsResponseParams>>; - -export interface RulePreviewLogs { - errors: string[]; - warnings: string[]; - startedAt?: string; - duration: number; -} - -export interface PreviewResponse { - previewId: string | undefined; - logs: RulePreviewLogs[] | undefined; - isAborted: boolean | undefined; -} diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_type_dependents.ts deleted file mode 100644 index 518937193cc4f..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/update_rules_type_dependents.ts +++ /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 type { UpdateRulesSchema } from './rule_schemas'; - -export const validateTimelineId = (rule: UpdateRulesSchema): string[] => { - if (rule.timeline_id != null) { - if (rule.timeline_title == null) { - return ['when "timeline_id" exists, "timeline_title" must also exist']; - } else if (rule.timeline_id === '') { - return ['"timeline_id" cannot be an empty string']; - } else { - return []; - } - } - return []; -}; - -export const validateTimelineTitle = (rule: UpdateRulesSchema): string[] => { - if (rule.timeline_title != null) { - if (rule.timeline_id == null) { - return ['when "timeline_title" exists, "timeline_id" must also exist']; - } else if (rule.timeline_title === '') { - return ['"timeline_title" cannot be an empty string']; - } else { - return []; - } - } - return []; -}; - -export const validateId = (rule: UpdateRulesSchema): string[] => { - if (rule.id != null && rule.rule_id != null) { - return ['both "id" and "rule_id" cannot exist, choose one or the other']; - } else if (rule.id == null && rule.rule_id == null) { - return ['either "id" or "rule_id" must be set']; - } else { - return []; - } -}; - -export const validateThreshold = (rule: UpdateRulesSchema): string[] => { - const errors: string[] = []; - if (rule.type === 'threshold') { - if (!rule.threshold) { - errors.push('when "type" is "threshold", "threshold" is required'); - } else { - if ( - rule.threshold.cardinality?.length && - rule.threshold.field.includes(rule.threshold.cardinality[0].field) - ) { - errors.push('Cardinality of a field that is being aggregated on is always 1'); - } - if (Array.isArray(rule.threshold.field) && rule.threshold.field.length > 3) { - errors.push('Number of fields must be 3 or less'); - } - } - } - return errors; -}; - -export const updateRuleValidateTypeDependents = (rule: UpdateRulesSchema): string[] => { - return [ - ...validateId(rule), - ...validateTimelineId(rule), - ...validateTimelineTitle(rule), - ...validateThreshold(rule), - ]; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.ts index d6e1faa7a5180..2c1cf288afe03 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/error_schema.ts @@ -8,13 +8,19 @@ import { NonEmptyString } from '@kbn/securitysolution-io-ts-types'; import * as t from 'io-ts'; -import { rule_id, status_code, message } from '../common/schemas'; +import { RuleSignatureId } from '../../rule_schema'; +import { status_code, message } from '../common/schemas'; // We use id: t.string intentionally and _never_ the id from global schemas as // sometimes echo back out the id that the user gave us and it is not guaranteed // to be a UUID but rather just a string const partial = t.exact( - t.partial({ id: t.string, rule_id, list_id: NonEmptyString, item_id: NonEmptyString }) + t.partial({ + id: t.string, + rule_id: RuleSignatureId, + list_id: NonEmptyString, + item_id: NonEmptyString, + }) ); const required = t.exact( t.type({ diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts index 5c934b0d2e040..b20a956525e2e 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts @@ -6,9 +6,3 @@ */ export * from './error_schema'; -export * from './get_installed_integrations_response_schema'; -export * from './find_exception_list_references_schema'; -export * from './import_rules_schema'; -export * from './prepackaged_rules_schema'; -export * from './prepackaged_rules_status_schema'; -export * from './rules_bulk_schema'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.ts deleted file mode 100644 index 50c36075a0cf2..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_schema.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. - */ - -import * as t from 'io-ts'; - -import { - rules_installed, - rules_updated, - timelines_installed, - timelines_updated, -} from '../common/schemas'; - -const prePackagedRulesSchema = t.type({ - rules_installed, - rules_updated, -}); - -const prePackagedTimelinesSchema = t.type({ - timelines_installed, - timelines_updated, -}); - -export const prePackagedRulesAndTimelinesSchema = t.exact( - t.intersection([prePackagedRulesSchema, prePackagedTimelinesSchema]) -); - -export type PrePackagedRulesAndTimelinesSchema = t.TypeOf< - typeof prePackagedRulesAndTimelinesSchema ->; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.ts deleted file mode 100644 index 9f5cc5542bc7c..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/prepackaged_rules_status_schema.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 * as t from 'io-ts'; - -import { - rules_installed, - rules_custom_installed, - rules_not_installed, - rules_not_updated, - timelines_installed, - timelines_not_installed, - timelines_not_updated, -} from '../common/schemas'; - -export const prePackagedTimelinesStatusSchema = t.type({ - timelines_installed, - timelines_not_installed, - timelines_not_updated, -}); - -const prePackagedRulesStatusSchema = t.type({ - rules_custom_installed, - rules_installed, - rules_not_installed, - rules_not_updated, -}); - -export const prePackagedRulesAndTimelinesStatusSchema = t.exact( - t.intersection([prePackagedRulesStatusSchema, prePackagedTimelinesStatusSchema]) -); - -export type PrePackagedRulesAndTimelinesStatusSchema = t.TypeOf< - typeof prePackagedRulesAndTimelinesStatusSchema ->; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.ts deleted file mode 100644 index 65c55f356c44b..0000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_bulk_schema.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 * as t from 'io-ts'; - -import { fullResponseSchema } from '../request'; -import { errorSchema } from './error_schema'; - -export const rulesBulkSchema = t.array(t.union([fullResponseSchema, errorSchema])); -export type RulesBulkSchema = t.TypeOf<typeof rulesBulkSchema>; 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 afa5a3950921b..36f7ee8977f5f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -16,7 +16,7 @@ import type { import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { hasLargeValueList } from '@kbn/securitysolution-list-utils'; -import type { Threshold, ThresholdNormalized } from './schemas/common'; +import type { Threshold, ThresholdNormalized } from './rule_schema'; export const hasLargeValueItem = ( exceptionItems: Array<ExceptionListItemSchema | CreateExceptionListItemSchema> diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts index 8061057280eea..078adada6d2b6 100644 --- a/x-pack/plugins/security_solution/common/endpoint/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts @@ -42,8 +42,8 @@ export const policyIndexPattern = 'metrics-endpoint.policy-*'; export const telemetryIndexPattern = 'metrics-endpoint.telemetry-*'; // File storage indexes supporting endpoint Upload/download -export const FILE_STORAGE_METADATA_INDEX = '.fleet-files'; -export const FILE_STORAGE_DATA_INDEX = '.fleet-file_data'; +export const FILE_STORAGE_METADATA_INDEX = '.fleet-endpoint-files'; +export const FILE_STORAGE_DATA_INDEX = '.fleet-endpoint-file-data'; // Endpoint API routes export const BASE_ENDPOINT_ROUTE = '/api/endpoint'; @@ -73,6 +73,8 @@ export const GET_FILE_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/get_file`; export const ENDPOINT_ACTION_LOG_ROUTE = `${BASE_ENDPOINT_ROUTE}/action_log/{agent_id}`; export const ACTION_STATUS_ROUTE = `${BASE_ENDPOINT_ROUTE}/action_status`; export const ACTION_DETAILS_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/{action_id}`; +export const ACTION_AGENT_FILE_INFO_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/{action_id}/{agent_id}/file`; +export const ACTION_AGENT_FILE_DOWNLOAD_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/{action_id}/{agent_id}/file/download`; export const ENDPOINTS_ACTION_LIST_ROUTE = `${BASE_ENDPOINT_ROUTE}/action`; export const failedFleetActionErrorCode = '424'; diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts index 868129f3a6737..01ecc95524b27 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/base_data_generator.ts @@ -68,6 +68,41 @@ const USERS = [ 'Genevieve', ]; +const toEsSearchHit = <T extends object = object>( + hitSource: T, + index: string = 'some-index' +): estypes.SearchHit<T> => { + return { + _index: index, + _id: '123', + _score: 1.0, + _source: hitSource, + }; +}; + +const toEsSearchResponse = <T extends object = object>( + hitsSource: Array<estypes.SearchHit<T>> +): estypes.SearchResponse<T> => { + return { + took: 3, + timed_out: false, + _shards: { + total: 2, + successful: 2, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: hitsSource.length, + relation: 'eq', + }, + max_score: 0, + hits: hitsSource, + }, + }; +}; + /** * A generic base class to assist in creating domain specific data generators. It includes * several general purpose random data generators for use within the class and exposes one @@ -193,12 +228,17 @@ export class BaseDataGenerator<GeneratedDoc extends {} = {}> { hitSource: T, index: string = 'some-index' ): estypes.SearchHit<T> { - return { - _index: index, - _id: this.seededUUIDv4(), - _score: 1.0, - _source: hitSource, - }; + const hit = toEsSearchHit<T>(hitSource, index); + hit._id = this.seededUUIDv4(); + + return hit; + } + + static toEsSearchHit<T extends object = object>( + hitSource: T, + index: string = 'some-index' + ): estypes.SearchHit<T> { + return toEsSearchHit<T>(hitSource, index); } /** @@ -209,23 +249,12 @@ export class BaseDataGenerator<GeneratedDoc extends {} = {}> { toEsSearchResponse<T extends object = object>( hitsSource: Array<estypes.SearchHit<T>> ): estypes.SearchResponse<T> { - return { - took: 3, - timed_out: false, - _shards: { - total: 2, - successful: 2, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: hitsSource.length, - relation: 'eq', - }, - max_score: 0, - hits: hitsSource, - }, - }; + return toEsSearchResponse<T>(hitsSource); + } + + static toEsSearchResponse<T extends object = object>( + hitsSource: Array<estypes.SearchHit<T>> + ): estypes.SearchResponse<T> { + return toEsSearchResponse<T>(hitsSource); } } diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts index e9531e48d0784..f8af9d8005893 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts @@ -88,7 +88,7 @@ export class EndpointActionGenerator extends BaseDataGenerator { output = { type: 'json', content: { - code: 'ra_get-file_success', + code: 'ra_get-file_success_done', path: '/some/path/bad_file.txt', size: 1234, zip_size: 123, diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts index 1743b50ffecd0..310fbcbea326b 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts @@ -127,3 +127,25 @@ export const EndpointActionGetFileSchema = { }; export type ResponseActionGetFileRequestBody = TypeOf<typeof EndpointActionGetFileSchema.body>; + +/** Schema that validates the file download API */ +export const EndpointActionFileDownloadSchema = { + params: schema.object({ + action_id: schema.string({ minLength: 1 }), + agent_id: schema.string({ minLength: 1 }), + }), +}; + +export type EndpointActionFileDownloadParams = TypeOf< + typeof EndpointActionFileDownloadSchema.params +>; + +/** Schema that validates the file info API */ +export const EndpointActionFileInfoSchema = { + params: schema.object({ + action_id: schema.string({ minLength: 1 }), + agent_id: schema.string({ minLength: 1 }), + }), +}; + +export type EndpointActionFileInfoParams = TypeOf<typeof EndpointActionFileInfoSchema.params>; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/get_file_download_id.ts b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/get_file_download_id.ts new file mode 100644 index 0000000000000..12d74207c57b9 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/get_file_download_id.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ActionDetails } from '../../types'; + +/** + * Constructs a file ID for a given agent. + * @param action + * @param agentId + */ +export const getFileDownloadId = (action: ActionDetails, agentId?: string): string => { + const { id: actionId, agents } = action; + + if (agentId && !agents.includes(agentId)) { + throw new Error(`Action [${actionId}] was not sent to agent id [${agentId}]`); + } + + return `${actionId}.${agentId ?? agents[0]}`; +}; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index 2d455bb6a839f..8ac8745a5d35b 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -6,6 +6,7 @@ */ import type { TypeOf } from '@kbn/config-schema'; +import type { FileJSON } from '@kbn/files-plugin/common'; import type { ActionStatusRequestSchema, NoParametersRequestSchema, @@ -360,3 +361,12 @@ export interface ActionListApiResponse { statuses: ResponseActionStatus[] | undefined; total: number; } + +export type UploadedFileInfo = Pick< + FileJSON, + 'name' | 'id' | 'mimeType' | 'size' | 'status' | 'created' +>; + +export interface ActionFileInfoApiResponse { + data: UploadedFileInfo; +} diff --git a/x-pack/plugins/security_solution/common/endpoint/types/file_storage.ts b/x-pack/plugins/security_solution/common/endpoint/types/file_storage.ts deleted file mode 100644 index 9ed8065197921..0000000000000 --- a/x-pack/plugins/security_solution/common/endpoint/types/file_storage.ts +++ /dev/null @@ -1,49 +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. - */ - -/** - * The Metadata information about a file that was uploaded by Endpoint - * as a result of a `get-file` response action - */ -export interface UploadedFile { - file: { - /** The chunk size used for each chunk in this file */ - ChunkSize?: number; - /** - * - `AWAITING_UPLOAD`: file metadata has been created. File is ready to be uploaded. - * - `UPLOADING`: file contents are being uploaded. - * - `READY`: file has been uploaded, successfully, without errors. - * - `UPLOAD_ERROR`: an error happened while the file was being uploaded, file contents - * are most likely corrupted. - * - `DELETED`: file is deleted. Files can be marked as deleted before the actual deletion - * of the contents and metadata happens. Deleted files should be treated as if they don’t - * exist. Only files in READY state can transition into DELETED state. - */ - Status: 'AWAITING_UPLOAD' | 'UPLOADING' | 'READY' | 'UPLOAD_ERROR' | 'DELETED'; - /** File extension (if any) */ - extension?: string; - hash?: { - md5?: string; - sha1?: string; - sha256?: string; - sha384?: string; - sha512?: string; - ssdeep?: string; - tlsh?: string; - }; - mime_type?: string; - mode?: string; - /** File name */ - name: string; - /** The full path to the file on the host machine */ - path: string; - /** The total size in bytes */ - size: number; - created?: string; - type: string; - }; -} diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts index cefd43fe5f99e..2c1743e262ead 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts @@ -9,10 +9,13 @@ import type { IEsSearchRequest, IEsSearchResponse } from '@kbn/data-plugin/commo import type { ESQuery } from '../../../../typed_json'; import type { Inspect, Maybe, SortField, TimerangeInput } from '../../../common'; +import type { RiskScoreEntity } from '../common'; export interface RiskScoreRequestOptions extends IEsSearchRequest { defaultIndex: string[]; + riskScoreEntity: RiskScoreEntity; timerange?: TimerangeInput; + includeAlertsCount?: boolean; onlyLatest?: boolean; pagination?: { cursorStart: number; @@ -47,6 +50,7 @@ export interface HostRiskScore { name: string; risk: RiskStats; }; + alertsCount?: number; } export interface UserRiskScore { @@ -55,6 +59,7 @@ export interface UserRiskScore { name: string; risk: RiskStats; }; + alertsCount?: number; } export interface RuleRisk { @@ -73,6 +78,7 @@ export const enum RiskScoreFields { userName = 'user.name', userRiskScore = 'user.risk.calculated_score_norm', userRisk = 'user.risk.calculated_level', + alertsCount = 'alertsCount', } export interface RiskScoreItem { @@ -85,6 +91,8 @@ export interface RiskScoreItem { [RiskScoreFields.hostRiskScore]: Maybe<number>; [RiskScoreFields.userRiskScore]: Maybe<number>; + + [RiskScoreFields.alertsCount]: Maybe<number>; } export const enum RiskSeverity { diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index 938374b622e79..64621f0a0598d 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -14,8 +14,11 @@ import { NoteSavedObjectToReturnRuntimeType } from './note'; import type { PinnedEvent } from './pinned_event'; import { PinnedEventToReturnSavedObjectRuntimeType } from './pinned_event'; import { - alias_purpose as savedObjectResolveAliasPurpose, - outcome as savedObjectResolveOutcome, + SavedObjectResolveAliasPurpose, + SavedObjectResolveAliasTargetId, + SavedObjectResolveOutcome, +} from '../../detection_engine/rule_schema'; +import { success, success_count as successCount, } from '../../detection_engine/schemas/common/schemas'; @@ -375,11 +378,11 @@ export type SingleTimelineResponse = runtimeTypes.TypeOf<typeof SingleTimelineRe export const ResolvedTimelineSavedObjectToReturnObjectRuntimeType = runtimeTypes.intersection([ runtimeTypes.type({ timeline: TimelineSavedToReturnObjectRuntimeType, - outcome: savedObjectResolveOutcome, + outcome: SavedObjectResolveOutcome, }), runtimeTypes.partial({ - alias_target_id: runtimeTypes.string, - alias_purpose: savedObjectResolveAliasPurpose, + alias_target_id: SavedObjectResolveAliasTargetId, + alias_purpose: SavedObjectResolveAliasPurpose, }), ]); diff --git a/x-pack/plugins/security_solution/common/types/timeline/store.ts b/x-pack/plugins/security_solution/common/types/timeline/store.ts index af7662122b5d3..047c8d20e15b7 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/store.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/store.ts @@ -61,6 +61,7 @@ export interface TimelinePersistInput { timelineType?: TimelineTypeLiteral; templateTimelineId?: string | null; templateTimelineVersion?: number | null; + title?: string; } /** Invoked when a column is sorted */ diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts index c9f5e57fb2285..943a3e5e236fa 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/prebuilt_rules.cy.ts @@ -8,7 +8,7 @@ import { COLLAPSED_ACTION_BTN, ELASTIC_RULES_BTN, - RELOAD_PREBUILT_RULES_BTN, + LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN, RULES_EMPTY_PROMPT, RULES_MONITORING_TABLE, RULES_ROW, @@ -111,12 +111,15 @@ describe('Prebuilt rules', () => { 'have.text', `Elastic rules (${expectedNumberOfRulesAfterDeletion})` ); - cy.get(RELOAD_PREBUILT_RULES_BTN).should('exist'); - cy.get(RELOAD_PREBUILT_RULES_BTN).should('have.text', 'Install 1 Elastic prebuilt rule '); + cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should('exist'); + cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should( + 'have.text', + 'Install 1 Elastic prebuilt rule ' + ); reloadDeletedRules(); - cy.get(RELOAD_PREBUILT_RULES_BTN).should('not.exist'); + cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should('not.exist'); cy.get(ELASTIC_RULES_BTN).should( 'have.text', @@ -134,8 +137,8 @@ describe('Prebuilt rules', () => { selectNumberOfRules(numberOfRulesToBeSelected); deleteSelectedRules(); - cy.get(RELOAD_PREBUILT_RULES_BTN).should('exist'); - cy.get(RELOAD_PREBUILT_RULES_BTN).should( + cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should('exist'); + cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should( 'have.text', `Install ${numberOfRulesToBeSelected} Elastic prebuilt rules ` ); @@ -146,7 +149,7 @@ describe('Prebuilt rules', () => { reloadDeletedRules(); - cy.get(RELOAD_PREBUILT_RULES_BTN).should('not.exist'); + cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).should('not.exist'); cy.get(ELASTIC_RULES_BTN).should( 'have.text', diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index 53cbdc7e84c48..59464a3ceda2f 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -5,11 +5,11 @@ * 2.0. */ -import type { Throttle } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { RuleActionThrottle } from '@kbn/securitysolution-io-ts-alerting-types'; import { getMockThreatData } from '../../public/detections/mitre/mitre_tactics_techniques'; import type { CompleteTimeline } from './timeline'; import { getTimeline, getIndicatorMatchTimelineTemplate } from './timeline'; -import type { FullResponseSchema } from '../../common/detection_engine/schemas/request'; +import type { RuleResponse } from '../../common/detection_engine/rule_schema'; import type { Connectors } from './connector'; const ccsRemoteName: string = Cypress.env('CCS_REMOTE_NAME'); @@ -36,7 +36,7 @@ interface Interval { } export interface Actions { - throttle: Throttle; + throttle: RuleActionThrottle; connectors: Connectors[]; } @@ -506,9 +506,7 @@ export const getEditedRule = (): CustomRule => ({ tags: [...(getExistingRule().tags || []), 'edited'], }); -export const expectedExportedRule = ( - ruleResponse: Cypress.Response<FullResponseSchema> -): string => { +export const expectedExportedRule = (ruleResponse: Cypress.Response<RuleResponse>): string => { const { id, updated_at: updatedAt, @@ -531,7 +529,7 @@ export const expectedExportedRule = ( // NOTE: Order of the properties in this object matters for the tests to work. // TODO: Follow up https://github.com/elastic/kibana/pull/137628 and add an explicit type to this object // without using Partial - const rule: Partial<FullResponseSchema> = { + const rule: Partial<RuleResponse> = { id, updated_at: updatedAt, updated_by: updatedBy, diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index 8b717d7f6bd22..5452d59b68c07 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -58,8 +58,6 @@ export const RULES_TABLE_AUTOREFRESH_INDICATOR = '[data-test-subj="loadingRulesI export const RISK_SCORE = '[data-test-subj="riskScore"]'; -export const RELOAD_PREBUILT_RULES_BTN = '[data-test-subj="reloadPrebuiltRulesBtn"]'; - export const SECOND_RULE = 1; export const RULE_CHECKBOX = '.euiTableRow .euiCheckbox__input'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index 13c0c7a7735bc..2bc4badf3cfa1 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -18,7 +18,6 @@ import { RULES_TABLE_REFRESH_INDICATOR, RULES_TABLE_AUTOREFRESH_INDICATOR, PAGINATION_POPOVER_BTN, - RELOAD_PREBUILT_RULES_BTN, RULE_CHECKBOX, RULE_NAME, RULE_SWITCH, @@ -194,7 +193,7 @@ export const openIntegrationsPopover = () => { }; export const reloadDeletedRules = () => { - cy.get(RELOAD_PREBUILT_RULES_BTN).click({ force: true }); + cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN).click({ force: true }); }; /** diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts index 57197fe512946..f8a48dd9c8239 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/prebuilt_rules.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { PrePackagedRulesStatusResponse } from '../../../public/detections/containers/detection_engine/rules/types'; +import type { PrePackagedRulesStatusResponse } from '../../../public/detection_engine/rule_management/logic/types'; export const getPrebuiltRulesStatus = () => { return cy.request<PrePackagedRulesStatusResponse>({ diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index 82eb2f9255b0f..099736e4f1003 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { Actions } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { RuleActionArray } from '@kbn/securitysolution-io-ts-alerting-types'; import type { CustomRule, @@ -258,7 +258,7 @@ export const createCustomRuleEnabled = ( ruleId = '1', interval = '100m', maxSignals = 500, - actions?: Actions + actions?: RuleActionArray ) => { const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined; const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined; diff --git a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts index bbfadc337f5e2..3d4f25d248e6a 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts @@ -136,7 +136,7 @@ export const goBackToAllRulesTable = () => { }; export const getDetails = (title: string | RegExp) => - cy.get(DETAILS_TITLE).contains(title).next(DETAILS_DESCRIPTION); + cy.contains(DETAILS_TITLE, title).next(DETAILS_DESCRIPTION); export const assertDetailsNotExist = (title: string | RegExp) => cy.get(DETAILS_TITLE).contains(title).should('not.exist'); diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index 4dce859a3efd6..4a6e3a105ee72 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -31,7 +31,8 @@ "timelines", "triggersActionsUi", "uiActions", - "unifiedSearch" + "unifiedSearch", + "files" ], "optionalPlugins": [ "cloudExperiments", diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx index 9e4de0cbf9c0e..5db5185679ab3 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx @@ -76,7 +76,7 @@ const CaseContainerComponent: React.FC = () => { selected_endpoint: endpointId, }), }); - + // TO-DO: onComponentInitialized not needed after removing the expandedEvent state from timeline const onComponentInitialized = useCallback(() => { dispatch( timelineActions.createTimeline({ diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx index 67d9be1817549..2be5960d92a32 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx @@ -58,7 +58,7 @@ describe('DraggableWrapper', () => { const mount = useMountAppended(); beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterEach(() => { @@ -66,7 +66,9 @@ describe('DraggableWrapper', () => { if (portal != null) { portal.innerHTML = ''; } + }); + afterAll(() => { jest.useRealTimers(); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx index e860713e5ce0c..662e1983680bd 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx @@ -11,7 +11,7 @@ import { waitFor, render, act } from '@testing-library/react'; import { AlertSummaryView } from './alert_summary_view'; import { mockAlertDetailsData } from './__mocks__'; import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; -import { useRuleWithFallback } from '../../../detections/containers/detection_engine/rules/use_rule_with_fallback'; +import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback'; import { TestProviders, TestProvidersComponent } from '../../mock'; import { TimelineId } from '../../../../common/types'; @@ -20,7 +20,7 @@ import * as i18n from './translations'; jest.mock('../../lib/kibana'); -jest.mock('../../../detections/containers/detection_engine/rules/use_rule_with_fallback', () => { +jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback', () => { return { useRuleWithFallback: jest.fn(), }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index 3da1cfdb3c6de..2b8c0d534228d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -51,7 +51,7 @@ mockUseGetUserCasesPermissions.mockImplementation(originalKibanaLib.useGetUserCa jest.mock('../../containers/cti/event_enrichment'); -jest.mock('../../../detections/containers/detection_engine/rules/use_rule_with_fallback', () => { +jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback', () => { return { useRuleWithFallback: jest.fn().mockReturnValue({ rule: { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/investigation_guide_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/investigation_guide_view.tsx index 4e9ff49a2b1dd..5e2827c396f74 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/investigation_guide_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/investigation_guide_view.tsx @@ -13,7 +13,7 @@ import styled from 'styled-components'; import type { GetBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; import * as i18n from './translations'; -import { useRuleWithFallback } from '../../../detections/containers/detection_engine/rules/use_rule_with_fallback'; +import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback'; import { MarkdownRenderer } from '../markdown_editor'; import { LineClamp } from '../line_clamp'; import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx index bb6b8cd5dbeab..35f48c9702333 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx @@ -80,6 +80,7 @@ describe('EventsQueryTabBody', () => { type: HostsType.page, endDate: new Date('2000').toISOString(), startDate: new Date('2000').toISOString(), + additionalFilters: [], }; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx index 68426e6a4b474..e8f9478ba4578 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx @@ -52,10 +52,9 @@ export const ALERTS_EVENTS_HISTOGRAM_ID = 'alertsOrEventsHistogramQuery'; type QueryTabBodyProps = UserQueryTabBodyProps | HostQueryTabBodyProps | NetworkQueryTabBodyProps; export type EventsQueryTabBodyComponentProps = QueryTabBodyProps & { + additionalFilters: Filter[]; deleteQuery?: GlobalTimeArgs['deleteQuery']; indexNames: string[]; - pageFilters?: Filter[]; - externalAlertPageFilters?: Filter[]; setQuery: GlobalTimeArgs['setQuery']; tableId: TableId; }; @@ -63,12 +62,11 @@ export type EventsQueryTabBodyComponentProps = QueryTabBodyProps & { const EXTERNAL_ALERTS_URL_PARAM = 'onlyExternalAlerts'; const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> = ({ + additionalFilters, deleteQuery, endDate, filterQuery, indexNames, - externalAlertPageFilters = [], - pageFilters = [], setQuery, startDate, tableId, @@ -110,6 +108,7 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> = } : c ), + title: i18n.EVENTS_GRAPH_TITLE, }) ); }, [dispatch, showExternalAlerts, tGridEnabled, tableId]); @@ -122,7 +121,7 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> = }; }, [deleteQuery]); - const additionalFilters = useMemo( + const toggleExternalAlertsCheckbox = useMemo( () => ( <EuiCheckbox id="showExternalAlertsCheckbox" @@ -146,11 +145,8 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> = ); const composedPageFilters = useMemo( - () => [ - ...pageFilters, - ...(showExternalAlerts ? [defaultAlertsFilters, ...externalAlertPageFilters] : []), - ], - [showExternalAlerts, externalAlertPageFilters, pageFilters] + () => (showExternalAlerts ? [defaultAlertsFilters, ...additionalFilters] : additionalFilters), + [additionalFilters, showExternalAlerts] ); return ( @@ -168,7 +164,7 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> = /> )} <StatefulEventsViewer - additionalFilters={additionalFilters} + additionalFilters={toggleExternalAlertsCheckbox} defaultCellActions={defaultCellActions} start={startDate} end={endDate} diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx index 822d4b7cc794d..a291265bc234d 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx @@ -23,7 +23,7 @@ import React, { useCallback, useState } from 'react'; import type { ImportDataResponse, ImportDataProps, -} from '../../../detections/containers/detection_engine/rules'; +} from '../../../detection_engine/rule_management/logic'; import { useAppToasts } from '../../hooks/use_app_toasts'; import * as i18n from './translations'; import { showToasterMessage } from './utils'; @@ -48,6 +48,7 @@ interface ImportDataModalProps { /** * Modal component for importing Rules from a json file */ +// TODO split into two: timelines and rules export const ImportDataModalComponent = ({ checkBoxLabel, closeModal, diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.ts b/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.ts index d6158dfe97ff6..8211c01a7bffa 100644 --- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/utils.ts @@ -14,7 +14,7 @@ import type { ImportResponseError, ImportRulesResponseError, ExceptionsImportError, -} from '../../../detections/containers/detection_engine/rules'; +} from '../../../detection_engine/rule_management/logic'; export const formatError = ( i18nFailedDetailedMessage: (message: string) => string, diff --git a/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.test.ts b/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.test.ts index 49be1077da393..430fddcd36fc6 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.test.ts @@ -4,27 +4,26 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -// import React from 'react'; -import type { RenderResult } from '@testing-library/react-hooks'; import { act, renderHook } from '@testing-library/react-hooks'; import { TestProviders } from '../../../mock'; -import type { Refetch } from '../../../store/inputs/model'; -import type { AnomaliesCount } from './use_anomalies_search'; -import { useNotableAnomaliesSearch, AnomalyJobStatus, AnomalyEntity } from './use_anomalies_search'; +import { useNotableAnomaliesSearch, AnomalyEntity } from './use_anomalies_search'; const jobId = 'auth_rare_source_ip_for_a_user'; const from = 'now-24h'; const to = 'now'; -const JOBS = [{ id: jobId, jobState: 'started', datafeedState: 'started' }]; +const job = { id: jobId, jobState: 'started', datafeedState: 'started' }; +const JOBS = [job]; +const useSecurityJobsRefetch = jest.fn(); -const mockuseInstalledSecurityJobs = jest.fn().mockReturnValue({ +const mockUseSecurityJobs = jest.fn().mockReturnValue({ loading: false, - isMlUser: true, + isMlAdmin: true, jobs: JOBS, + refetch: useSecurityJobsRefetch, }); -jest.mock('../hooks/use_installed_security_jobs', () => ({ - useInstalledSecurityJobs: () => mockuseInstalledSecurityJobs(), +jest.mock('../../ml_popover/hooks/use_security_jobs', () => ({ + useSecurityJobs: () => mockUseSecurityJobs(), })); const mockAddToastError = jest.fn(); @@ -72,14 +71,7 @@ describe('useNotableAnomaliesSearch', () => { expect(mockNotableAnomaliesSearch).not.toHaveBeenCalled(); }); - it('refetch calls notableAnomaliesSearch', async () => { - let renderResult: RenderResult<{ - isLoading: boolean; - data: AnomaliesCount[]; - refetch: Refetch; - }>; - - // first notableAnomaliesSearch call + it('refetch calls useSecurityJobs().refetch', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook( () => useNotableAnomaliesSearch({ skip: false, from, to }), @@ -87,17 +79,13 @@ describe('useNotableAnomaliesSearch', () => { wrapper: TestProviders, } ); - renderResult = result; await waitForNextUpdate(); - }); - await act(async () => { - mockNotableAnomaliesSearch.mockClear(); // clear the first notableAnomaliesSearch call - await renderResult.current.refetch(); + result.current.refetch(); }); - expect(mockNotableAnomaliesSearch).toHaveBeenCalled(); + expect(useSecurityJobsRefetch).toHaveBeenCalled(); }); it('returns formated data', async () => { @@ -119,9 +107,8 @@ describe('useNotableAnomaliesSearch', () => { expect.arrayContaining([ { count: 99, - jobId, name: jobId, - status: AnomalyJobStatus.enabled, + job, entity: AnomalyEntity.Host, }, ]) @@ -129,11 +116,28 @@ describe('useNotableAnomaliesSearch', () => { }); }); + it('does not throw error when aggregations is undefined', async () => { + await act(async () => { + mockNotableAnomaliesSearch.mockResolvedValue({}); + const { waitForNextUpdate } = renderHook( + () => useNotableAnomaliesSearch({ skip: false, from, to }), + { + wrapper: TestProviders, + } + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(mockAddToastError).not.toBeCalled(); + }); + }); + it('returns uninstalled jobs', async () => { - mockuseInstalledSecurityJobs.mockReturnValue({ + mockUseSecurityJobs.mockReturnValue({ loading: false, - isMlUser: true, + isMlAdmin: true, jobs: [], + refetch: useSecurityJobsRefetch, }); await act(async () => { @@ -153,9 +157,8 @@ describe('useNotableAnomaliesSearch', () => { expect.arrayContaining([ { count: 0, - jobId: undefined, - name: jobId, - status: AnomalyJobStatus.uninstalled, + name: job.id, + job: undefined, entity: AnomalyEntity.Host, }, ]) @@ -169,16 +172,18 @@ describe('useNotableAnomaliesSearch', () => { mockNotableAnomaliesSearch.mockResolvedValue({ aggregations: { number_of_anomalies: { buckets: [jobCount] } }, }); - mockuseInstalledSecurityJobs.mockReturnValue({ + + const customJob = { + id: customJobId, + jobState: 'started', + datafeedState: 'started', + }; + + mockUseSecurityJobs.mockReturnValue({ loading: false, - isMlUser: true, - jobs: [ - { - id: customJobId, - jobState: 'started', - datafeedState: 'started', - }, - ], + isMlAdmin: true, + jobs: [customJob], + refetch: useSecurityJobsRefetch, }); await act(async () => { @@ -196,9 +201,8 @@ describe('useNotableAnomaliesSearch', () => { expect.arrayContaining([ { count: 99, - jobId: customJobId, - name: jobId, - status: AnomalyJobStatus.enabled, + name: job.id, + job: customJob, entity: AnomalyEntity.Host, }, ]) @@ -209,6 +213,12 @@ describe('useNotableAnomaliesSearch', () => { it('returns the most recent job when there are multiple jobs matching one notable job id`', async () => { const mostRecentJobId = `mostRecent_${jobId}`; const leastRecentJobId = `leastRecent_${jobId}`; + const mostRecentJob = { + id: mostRecentJobId, + jobState: 'started', + datafeedState: 'started', + latestTimestampSortValue: 1661731200000, // 2022-08-29 + }; mockNotableAnomaliesSearch.mockResolvedValue({ aggregations: { @@ -221,9 +231,9 @@ describe('useNotableAnomaliesSearch', () => { }, }); - mockuseInstalledSecurityJobs.mockReturnValue({ + mockUseSecurityJobs.mockReturnValue({ loading: false, - isMlUser: true, + isMlAdmin: true, jobs: [ { id: leastRecentJobId, @@ -231,13 +241,9 @@ describe('useNotableAnomaliesSearch', () => { datafeedState: 'started', latestTimestampSortValue: 1661644800000, // 2022-08-28 }, - { - id: mostRecentJobId, - jobState: 'started', - datafeedState: 'started', - latestTimestampSortValue: 1661731200000, // 2022-08-29 - }, + mostRecentJob, ], + refetch: useSecurityJobsRefetch, }); await act(async () => { @@ -254,9 +260,8 @@ describe('useNotableAnomaliesSearch', () => { expect.arrayContaining([ { count: 99, - jobId: mostRecentJobId, name: jobId, - status: AnomalyJobStatus.enabled, + job: mostRecentJob, entity: AnomalyEntity.Host, }, ]) diff --git a/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.ts b/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.ts index 1a95f56465ba8..90025eb8d65fe 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/anomaly/use_anomalies_search.ts @@ -5,28 +5,20 @@ * 2.0. */ -import { useState, useEffect, useMemo, useRef } from 'react'; -import { filter, head, noop, orderBy, pipe, has } from 'lodash/fp'; -import type { MlSummaryJob } from '@kbn/ml-plugin/common'; +import { useState, useEffect, useMemo } from 'react'; +import { filter, head, orderBy, pipe, has } from 'lodash/fp'; import { DEFAULT_ANOMALY_SCORE } from '../../../../../common/constants'; import * as i18n from './translations'; import { useUiSetting$ } from '../../../lib/kibana'; import { useAppToasts } from '../../../hooks/use_app_toasts'; -import { useInstalledSecurityJobs } from '../hooks/use_installed_security_jobs'; import { notableAnomaliesSearch } from '../api/anomalies_search'; import type { NotableAnomaliesJobId } from '../../../../overview/components/entity_analytics/anomalies/config'; import { NOTABLE_ANOMALIES_IDS } from '../../../../overview/components/entity_analytics/anomalies/config'; import { getAggregatedAnomaliesQuery } from '../../../../overview/components/entity_analytics/anomalies/query'; import type { inputsModel } from '../../../store'; -import { isJobFailed, isJobStarted } from '../../../../../common/machine_learning/helpers'; - -export enum AnomalyJobStatus { - 'enabled', - 'disabled', - 'uninstalled', - 'failed', -} +import { useSecurityJobs } from '../../ml_popover/hooks/use_security_jobs'; +import type { SecurityJob } from '../../ml_popover/types'; export const enum AnomalyEntity { User, @@ -35,10 +27,9 @@ export const enum AnomalyEntity { export interface AnomaliesCount { name: NotableAnomaliesJobId; - jobId?: string; count: number; - status: AnomalyJobStatus; entity: AnomalyEntity; + job?: SecurityJob; } interface UseNotableAnomaliesSearchProps { @@ -57,19 +48,20 @@ export const useNotableAnomaliesSearch = ({ refetch: inputsModel.Refetch; } => { const [data, setData] = useState<AnomaliesCount[]>(formatResultData([], [])); - const refetch = useRef<inputsModel.Refetch>(noop); const { - loading: installedJobsLoading, - isMlUser, - jobs: installedSecurityJobs, - } = useInstalledSecurityJobs(); + loading: jobsLoading, + isMlAdmin: isMlUser, + jobs: securityJobs, + refetch: refetchJobs, + } = useSecurityJobs(); + const [loading, setLoading] = useState(true); const { addError } = useAppToasts(); const [anomalyScoreThreshold] = useUiSetting$<number>(DEFAULT_ANOMALY_SCORE); const { notableAnomaliesJobs, query } = useMemo(() => { - const newNotableAnomaliesJobs = installedSecurityJobs.filter(({ id }) => + const newNotableAnomaliesJobs = securityJobs.filter(({ id }) => NOTABLE_ANOMALIES_IDS.some((notableJobId) => matchJobId(id, notableJobId)) ); @@ -84,7 +76,7 @@ export const useNotableAnomaliesSearch = ({ query: newQuery, notableAnomaliesJobs: newNotableAnomaliesJobs, }; - }, [installedSecurityJobs, anomalyScoreThreshold, from, to]); + }, [securityJobs, anomalyScoreThreshold, from, to]); useEffect(() => { let isSubscribed = true; @@ -102,7 +94,7 @@ export const useNotableAnomaliesSearch = ({ try { const response = await notableAnomaliesSearch( { - jobIds: notableAnomaliesJobs.map(({ id }) => id), + jobIds: notableAnomaliesJobs.filter((job) => job.isInstalled).map(({ id }) => id), query, }, abortCtrl.signal @@ -110,7 +102,7 @@ export const useNotableAnomaliesSearch = ({ if (isSubscribed) { setLoading(false); - const buckets = response.aggregations.number_of_anomalies.buckets; + const buckets = response.aggregations?.number_of_anomalies.buckets ?? []; setData(formatResultData(buckets, notableAnomaliesJobs)); } } catch (error) { @@ -122,32 +114,14 @@ export const useNotableAnomaliesSearch = ({ } fetchAnomaliesSearch(); - refetch.current = fetchAnomaliesSearch; + return () => { isSubscribed = false; abortCtrl.abort(); }; - }, [skip, isMlUser, addError, query, notableAnomaliesJobs]); + }, [skip, isMlUser, addError, query, notableAnomaliesJobs, refetchJobs]); - return { isLoading: loading || installedJobsLoading, data, refetch: refetch.current }; -}; - -const getMLJobStatus = ( - notableJobId: NotableAnomaliesJobId, - job: MlSummaryJob | undefined, - notableAnomaliesJobs: MlSummaryJob[] -) => { - if (job) { - if (isJobStarted(job.jobState, job.datafeedState)) { - return AnomalyJobStatus.enabled; - } - if (isJobFailed(job.jobState, job.datafeedState)) { - return AnomalyJobStatus.failed; - } - } - return notableAnomaliesJobs.some(({ id }) => matchJobId(id, notableJobId)) - ? AnomalyJobStatus.disabled - : AnomalyJobStatus.uninstalled; + return { isLoading: loading || jobsLoading, data, refetch: refetchJobs }; }; function formatResultData( @@ -155,7 +129,7 @@ function formatResultData( key: string; doc_count: number; }>, - notableAnomaliesJobs: MlSummaryJob[] + notableAnomaliesJobs: SecurityJob[] ): AnomaliesCount[] { return NOTABLE_ANOMALIES_IDS.map((notableJobId) => { const job = findJobWithId(notableJobId)(notableAnomaliesJobs); @@ -164,10 +138,9 @@ function formatResultData( return { name: notableJobId, - jobId: job?.id, count: bucket?.doc_count ?? 0, - status: getMLJobStatus(notableJobId, job, notableAnomaliesJobs), entity: hasUserName ? AnomalyEntity.User : AnomalyEntity.Host, + job, }; }); } @@ -183,8 +156,8 @@ const matchJobId = (jobId: string, notableJobId: NotableAnomaliesJobId) => * When multiple jobs match a notable job id, it returns the most recent one. */ const findJobWithId = (notableJobId: NotableAnomaliesJobId) => - pipe<MlSummaryJob[][], MlSummaryJob[], MlSummaryJob[], MlSummaryJob | undefined>( - filter<MlSummaryJob>(({ id }) => matchJobId(id, notableJobId)), - orderBy<MlSummaryJob>('latestTimestampSortValue', 'desc'), + pipe<SecurityJob[][], SecurityJob[], SecurityJob[], SecurityJob | undefined>( + filter<SecurityJob>(({ id }) => matchJobId(id, notableJobId)), + orderBy<SecurityJob>('latestTimestampSortValue', 'desc'), head ); diff --git a/x-pack/plugins/security_solution/public/common/components/ml/api/anomalies_search.ts b/x-pack/plugins/security_solution/public/common/components/ml/api/anomalies_search.ts index 2431b3da3220d..966137c616f99 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/api/anomalies_search.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/api/anomalies_search.ts @@ -14,7 +14,7 @@ export interface Body { } export interface AnomaliesSearchResponse { - aggregations: { + aggregations?: { number_of_anomalies: { buckets: Array<{ key: NotableAnomaliesJobId; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/api/errors.ts b/x-pack/plugins/security_solution/public/common/components/ml/api/errors.ts index b289890bb4335..5fad08ed0979a 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/api/errors.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/api/errors.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { ErrorResponseBase } from '@elastic/elasticsearch/lib/api/types'; import { has } from 'lodash/fp'; import type { MlError } from '../types'; @@ -19,3 +20,6 @@ export interface MlStartJobError { // Otherwise for now, has will work ok even though it casts 'unknown' to 'any' export const isMlStartJobError = (value: unknown): value is MlStartJobError => has('error.msg', value) && has('error.response', value) && has('error.statusCode', value); + +export const isUnknownError = (value: unknown): value is ErrorResponseBase => + has('error.error.reason', value); diff --git a/x-pack/plugins/security_solution/public/common/components/ml/api/throw_if_not_ok.ts b/x-pack/plugins/security_solution/public/common/components/ml/api/throw_if_not_ok.ts index c4bd1888627f3..f88de77348322 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/api/throw_if_not_ok.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/api/throw_if_not_ok.ts @@ -8,7 +8,7 @@ import * as i18n from './translations'; import { ToasterError } from '../../toasters'; import type { SetupMlResponse } from '../../ml_popover/types'; -import { isMlStartJobError } from './errors'; +import { isMlStartJobError, isUnknownError } from './errors'; export const tryParseResponse = (response: string): string => { try { @@ -22,18 +22,21 @@ export const throwIfErrorAttachedToSetup = ( setupResponse: SetupMlResponse, jobIdErrorFilter: string[] = [] ): void => { - const jobErrors = setupResponse.jobs.reduce<string[]>( - (accum, job) => - job.error != null && jobIdErrorFilter.includes(job.id) - ? [ - ...accum, - job.error.msg, - tryParseResponse(job.error.response), - `${i18n.STATUS_CODE} ${job.error.statusCode}`, - ] - : accum, - [] - ); + const jobErrors = setupResponse.jobs.reduce<string[]>((accum, job) => { + if (job.error != null && jobIdErrorFilter.includes(job.id)) { + if (isMlStartJobError(job)) { + return [ + ...accum, + job.error.msg, + tryParseResponse(job.error.response), + `${i18n.STATUS_CODE} ${job.error.statusCode}`, + ]; + } else if (isUnknownError(job)) { + return [job.error.error.reason]; + } + } + return accum; + }, []); const dataFeedErrors = setupResponse.datafeeds.reduce<string[]>( (accum, dataFeed) => @@ -67,6 +70,8 @@ export const throwIfErrorAttached = ( tryParseResponse(dataFeed.error.response), `${i18n.STATUS_CODE} ${dataFeed.error.statusCode}`, ]; + } else if (isUnknownError(dataFeed)) { + return [dataFeed.error.error.reason]; } else { return accum; } diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs.ts index ec87d2e2c22a5..0fcbe7eb39850 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs.ts @@ -28,6 +28,7 @@ export interface UseGetInstalledJobReturn { isLicensed: boolean; } +// TODO react-query export const useGetInstalledJob = (jobIds: string[]): UseGetInstalledJobReturn => { const [jobs, setJobs] = useState<CombinedJobWithStats[]>([]); const { addError } = useAppToasts(); diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs_summary.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs_summary.ts index 129e41f575318..9d0a3870aca13 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs_summary.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_get_jobs_summary.ts @@ -10,4 +10,5 @@ import { getJobsSummary } from '../api/get_jobs_summary'; const _getJobsSummary = withOptionalSignal(getJobsSummary); +// TODO rewrite to react-query export const useGetJobsSummary = () => useAsync(_getJobsSummary); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/translations.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/translations.ts new file mode 100644 index 0000000000000..0553ab20985b1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/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 START_JOB_FAILURE = i18n.translate( + 'xpack.securitySolution.components.mlPopup.hooks.errors.startJobFailureTitle', + { + defaultMessage: 'Start job failure', + } +); + +export const STOP_JOB_FAILURE = i18n.translate( + 'xpack.securitySolution.components.mlPopup.hooks.errors.stopJobFailureTitle', + { + defaultMessage: 'Stop job failure', + } +); + +export const CREATE_JOB_FAILURE = i18n.translate( + 'xpack.securitySolution.components.mlPopup.hooks.errors.createJobFailureTitle', + { + defaultMessage: 'Create job failure', + } +); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx new file mode 100644 index 0000000000000..0f5289ae587d7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.test.tsx @@ -0,0 +1,146 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { renderHook, act } from '@testing-library/react-hooks'; +import { useEnableDataFeed } from './use_enable_data_feed'; +import { + createSecuritySolutionStorageMock, + kibanaObservable, + mockGlobalState, + SUB_PLUGINS_REDUCER, + TestProviders, +} from '../../../mock'; +import { createStore } from '../../../store'; +import type { State } from '../../../store'; + +import type { SecurityJob } from '../types'; +import { tGridReducer } from '@kbn/timelines-plugin/public'; + +const state: State = mockGlobalState; +const { storage } = createSecuritySolutionStorageMock(); +const store = createStore( + state, + SUB_PLUGINS_REDUCER, + { dataTable: tGridReducer }, + kibanaObservable, + storage +); + +const wrapper = ({ children }: { children: React.ReactNode }) => ( + <TestProviders store={store}>{children}</TestProviders> +); + +const TIMESTAMP = 99999999; +const JOB = { + isInstalled: false, + datafeedState: 'failed', + jobState: 'failed', + isCompatible: true, +} as SecurityJob; + +const mockSetupMlJob = jest.fn().mockReturnValue(Promise.resolve()); +const mockStartDatafeeds = jest.fn().mockReturnValue(Promise.resolve()); +const mockStopDatafeeds = jest.fn().mockReturnValue(Promise.resolve()); + +jest.mock('../api', () => ({ + setupMlJob: () => mockSetupMlJob(), + startDatafeeds: (...params: unknown[]) => mockStartDatafeeds(...params), + stopDatafeeds: () => mockStopDatafeeds(), +})); + +describe('useSecurityJobsHelpers', () => { + afterEach(() => { + mockSetupMlJob.mockReset(); + mockStartDatafeeds.mockReset(); + mockStopDatafeeds.mockReset(); + }); + + it('renders isLoading=true when installing job', async () => { + let resolvePromiseCb: (value: unknown) => void; + mockSetupMlJob.mockReturnValue( + new Promise((resolve) => { + resolvePromiseCb = resolve; + }) + ); + const { result, waitForNextUpdate } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + expect(result.current.isLoading).toBe(false); + + await act(async () => { + const enableDataFeedPromise = result.current.enableDatafeed(JOB, TIMESTAMP, false); + + await waitForNextUpdate(); + expect(result.current.isLoading).toBe(true); + + resolvePromiseCb({}); + await enableDataFeedPromise; + expect(result.current.isLoading).toBe(false); + }); + }); + + it('does not call setupMlJob if job is already installed', async () => { + mockSetupMlJob.mockReturnValue(Promise.resolve()); + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + + await act(async () => { + await result.current.enableDatafeed({ ...JOB, isInstalled: true }, TIMESTAMP, false); + }); + + expect(mockSetupMlJob).not.toBeCalled(); + }); + + it('calls setupMlJob if job is uninstalled', async () => { + mockSetupMlJob.mockReturnValue(Promise.resolve()); + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed({ ...JOB, isInstalled: false }, TIMESTAMP, false); + }); + expect(mockSetupMlJob).toBeCalled(); + }); + + it('calls startDatafeeds if enable param is true', async () => { + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed(JOB, TIMESTAMP, true); + }); + expect(mockStartDatafeeds).toBeCalled(); + expect(mockStopDatafeeds).not.toBeCalled(); + }); + + it('calls stopDatafeeds if enable param is false', async () => { + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed(JOB, TIMESTAMP, false); + }); + expect(mockStartDatafeeds).not.toBeCalled(); + expect(mockStopDatafeeds).toBeCalled(); + }); + + it('calls startDatafeeds with 2 weeks old start date', async () => { + jest.useFakeTimers('modern').setSystemTime(new Date('1989-03-07')); + + const { result } = renderHook(() => useEnableDataFeed(), { + wrapper, + }); + await act(async () => { + await result.current.enableDatafeed(JOB, TIMESTAMP, true); + }); + expect(mockStartDatafeeds).toBeCalledWith({ + datafeedIds: [`datafeed-undefined`], + start: new Date('1989-02-21').getTime(), + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.ts new file mode 100644 index 0000000000000..2d73d07c1787c --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_enable_data_feed.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 { useCallback, useState } from 'react'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; +import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../lib/telemetry'; +import { setupMlJob, startDatafeeds, stopDatafeeds } from '../api'; +import type { SecurityJob } from '../types'; +import * as i18n from './translations'; + +// Enable/Disable Job & Datafeed -- passed to JobsTable for use as callback on JobSwitch +export const useEnableDataFeed = () => { + const { addError } = useAppToasts(); + const [isLoading, setIsLoading] = useState(false); + + const enableDatafeed = useCallback( + async (job: SecurityJob, latestTimestampMs: number, enable: boolean) => { + submitTelemetry(job, enable); + + if (!job.isInstalled) { + setIsLoading(true); + try { + await setupMlJob({ + configTemplate: job.moduleId, + indexPatternName: job.defaultIndexPattern, + jobIdErrorFilter: [job.id], + groups: job.groups, + }); + setIsLoading(false); + } catch (error) { + addError(error, { title: i18n.CREATE_JOB_FAILURE }); + setIsLoading(false); + return; + } + } + + // Max start time for job is no more than two weeks ago to ensure job performance + const date = new Date(); + const maxStartTime = date.setDate(date.getDate() - 14); + + setIsLoading(true); + if (enable) { + const startTime = Math.max(latestTimestampMs, maxStartTime); + try { + await startDatafeeds({ datafeedIds: [`datafeed-${job.id}`], start: startTime }); + } catch (error) { + track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_ENABLE_FAILURE); + addError(error, { title: i18n.START_JOB_FAILURE }); + } + } else { + try { + await stopDatafeeds({ datafeedIds: [`datafeed-${job.id}`] }); + } catch (error) { + track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_DISABLE_FAILURE); + addError(error, { title: i18n.STOP_JOB_FAILURE }); + } + } + setIsLoading(false); + }, + [addError] + ); + + return { enableDatafeed, isLoading }; +}; + +const submitTelemetry = (job: SecurityJob, enabled: boolean) => { + // Report type of job enabled/disabled + track( + METRIC_TYPE.COUNT, + job.isElasticJob + ? enabled + ? TELEMETRY_EVENT.SIEM_JOB_ENABLED + : TELEMETRY_EVENT.SIEM_JOB_DISABLED + : enabled + ? TELEMETRY_EVENT.CUSTOM_JOB_ENABLED + : TELEMETRY_EVENT.CUSTOM_JOB_DISABLED + ); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts index db564d13456a0..23b284264387b 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts @@ -71,7 +71,7 @@ describe('useSecurityJobs', () => { bucketSpanSeconds: 900, }; - const { result, waitForNextUpdate } = renderHook(() => useSecurityJobs(false)); + const { result, waitForNextUpdate } = renderHook(() => useSecurityJobs()); await waitForNextUpdate(); expect(result.current.jobs).toHaveLength(6); @@ -79,7 +79,7 @@ describe('useSecurityJobs', () => { }); it('returns those permissions', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSecurityJobs(false)); + const { result, waitForNextUpdate } = renderHook(() => useSecurityJobs()); await waitForNextUpdate(); expect(result.current.isMlAdmin).toEqual(true); @@ -88,7 +88,7 @@ describe('useSecurityJobs', () => { it('renders a toast error if an ML call fails', async () => { (getModules as jest.Mock).mockRejectedValue('whoops'); - const { waitForNextUpdate } = renderHook(() => useSecurityJobs(false)); + const { waitForNextUpdate } = renderHook(() => useSecurityJobs()); await waitForNextUpdate(); expect(appToastsMock.addError).toHaveBeenCalledWith('whoops', { @@ -104,7 +104,7 @@ describe('useSecurityJobs', () => { }); it('returns empty jobs and false predicates', () => { - const { result } = renderHook(() => useSecurityJobs(false)); + const { result } = renderHook(() => useSecurityJobs()); expect(result.current.jobs).toEqual([]); expect(result.current.isMlAdmin).toEqual(false); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.ts index c7d2c07eec2dd..4f39fd1a746c8 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.ts @@ -5,8 +5,9 @@ * 2.0. */ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; +import { noop } from 'lodash/fp'; import { DEFAULT_INDEX_KEY } from '../../../../../common/constants'; import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license'; @@ -18,12 +19,14 @@ import { createSecurityJobs } from './use_security_jobs_helpers'; import { useMlCapabilities } from '../../ml/hooks/use_ml_capabilities'; import * as i18n from '../../ml/translations'; import { getJobsSummary } from '../../ml/api/get_jobs_summary'; +import type { inputsModel } from '../../../store'; export interface UseSecurityJobsReturn { loading: boolean; jobs: SecurityJob[]; isMlAdmin: boolean; isLicensed: boolean; + refetch: inputsModel.Refetch; } /** @@ -35,25 +38,24 @@ export interface UseSecurityJobsReturn { * NOTE: If the user is not an ml admin, jobs will be empty and isMlAdmin will be false. * If you only need installed jobs, try the {@link useInstalledSecurityJobs} hook. * - * @param refetchData */ -export const useSecurityJobs = (refetchData: boolean): UseSecurityJobsReturn => { +export const useSecurityJobs = (): UseSecurityJobsReturn => { const [jobs, setJobs] = useState<SecurityJob[]>([]); const [loading, setLoading] = useState(true); const mlCapabilities = useMlCapabilities(); const [securitySolutionDefaultIndex] = useUiSetting$<string[]>(DEFAULT_INDEX_KEY); const http = useHttp(); const { addError } = useAppToasts(); - + const refetch = useRef<inputsModel.Refetch>(noop); const isMlAdmin = hasMlAdminPermissions(mlCapabilities); const isLicensed = hasMlLicense(mlCapabilities); useEffect(() => { let isSubscribed = true; const abortCtrl = new AbortController(); - setLoading(true); async function fetchSecurityJobIdsFromGroupsData() { + setLoading(true); if (isMlAdmin && isLicensed) { try { // Batch fetch all installed jobs, ML modules, and check which modules are compatible with securitySolutionDefaultIndex @@ -87,11 +89,13 @@ export const useSecurityJobs = (refetchData: boolean): UseSecurityJobsReturn => } fetchSecurityJobIdsFromGroupsData(); + + refetch.current = fetchSecurityJobIdsFromGroupsData; return () => { isSubscribed = false; abortCtrl.abort(); }; - }, [refetchData, isMlAdmin, isLicensed, securitySolutionDefaultIndex, addError, http]); + }, [isMlAdmin, isLicensed, securitySolutionDefaultIndex, addError, http]); - return { isLicensed, isMlAdmin, jobs, loading }; + return { isLicensed, isMlAdmin, jobs, loading, refetch: refetch.current }; }; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx index accb9eb6d7387..ee16179c9c735 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.tsx @@ -13,17 +13,10 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import moment from 'moment'; -import type { Dispatch } from 'react'; -import React, { useCallback, useReducer, useState, useMemo } from 'react'; +import React, { useCallback, useState, useMemo } from 'react'; import styled from 'styled-components'; - import { MLJobsAwaitingNodeWarning } from '@kbn/ml-plugin/public'; import { useKibana } from '../../lib/kibana'; -import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../lib/telemetry'; -import type { ActionToaster } from '../toasters'; -import { errorToToaster, useStateToaster } from '../toasters'; -import { setupMlJob, startDatafeeds, stopDatafeeds } from './api'; import { filterJobs } from './helpers'; import { JobsTableFilters } from './jobs_table/filters/jobs_table_filters'; import { JobsTable } from './jobs_table/jobs_table'; @@ -33,6 +26,7 @@ import * as i18n from './translations'; import type { JobsFilters, SecurityJob } from './types'; import { UpgradeContents } from './upgrade_contents'; import { useSecurityJobs } from './hooks/use_security_jobs'; +import { useEnableDataFeed } from './hooks/use_enable_data_feed'; const PopoverContentsDiv = styled.div` max-width: 684px; @@ -44,49 +38,6 @@ const PopoverContentsDiv = styled.div` PopoverContentsDiv.displayName = 'PopoverContentsDiv'; -interface State { - isLoading: boolean; - refreshToggle: boolean; -} - -type Action = { type: 'refresh' } | { type: 'loading' } | { type: 'success' } | { type: 'failure' }; - -function mlPopoverReducer(state: State, action: Action): State { - switch (action.type) { - case 'refresh': { - return { - ...state, - refreshToggle: !state.refreshToggle, - }; - } - case 'loading': { - return { - ...state, - isLoading: true, - }; - } - case 'success': { - return { - ...state, - isLoading: false, - }; - } - case 'failure': { - return { - ...state, - isLoading: false, - }; - } - default: - return state; - } -} - -const initialState: State = { - isLoading: false, - refreshToggle: true, -}; - const defaultFilterProps: JobsFilters = { filterQuery: '', showCustomJobs: false, @@ -95,8 +46,6 @@ const defaultFilterProps: JobsFilters = { }; export const MlPopover = React.memo(() => { - const [{ isLoading, refreshToggle }, dispatch] = useReducer(mlPopoverReducer, initialState); - const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [filterProperties, setFilterProperties] = useState(defaultFilterProps); const { @@ -104,13 +53,18 @@ export const MlPopover = React.memo(() => { isLicensed, loading: isLoadingSecurityJobs, jobs, - } = useSecurityJobs(refreshToggle); - const [, dispatchToaster] = useStateToaster(); + refetch: refreshJobs, + } = useSecurityJobs(); + const docLinks = useKibana().services.docLinks; + const { enableDatafeed, isLoading: isLoadingEnableDataFeed } = useEnableDataFeed(); const handleJobStateChange = useCallback( - (job: SecurityJob, latestTimestampMs: number, enable: boolean) => - enableDatafeed(job, latestTimestampMs, enable, dispatch, dispatchToaster), - [dispatch, dispatchToaster] + async (job: SecurityJob, latestTimestampMs: number, enable: boolean) => { + const result = await enableDatafeed(job, latestTimestampMs, enable); + refreshJobs(); + return result; + }, + [refreshJobs, enableDatafeed] ); const filteredJobs = filterJobs({ @@ -169,7 +123,7 @@ export const MlPopover = React.memo(() => { iconSide="right" onClick={() => { setIsPopoverOpen(!isPopoverOpen); - dispatch({ type: 'refresh' }); + refreshJobs(); }} textProps={{ style: { fontSize: '1rem' } }} > @@ -211,7 +165,7 @@ export const MlPopover = React.memo(() => { rel="noopener noreferrer" target="_blank" > - {'Anomaly Detection with Machine Learning'} + {i18n.ANOMALY_DETECTION_DOCS} </a> ), }} @@ -225,7 +179,7 @@ export const MlPopover = React.memo(() => { <MLJobsAwaitingNodeWarning jobIds={installedJobsIds} /> <JobsTable - isLoading={isLoadingSecurityJobs || isLoading} + isLoading={isLoadingSecurityJobs || isLoadingEnableDataFeed} jobs={filteredJobs} onJobStateChange={handleJobStateChange} /> @@ -238,68 +192,4 @@ export const MlPopover = React.memo(() => { } }); -// Enable/Disable Job & Datafeed -- passed to JobsTable for use as callback on JobSwitch -const enableDatafeed = async ( - job: SecurityJob, - latestTimestampMs: number, - enable: boolean, - dispatch: Dispatch<Action>, - dispatchToaster: Dispatch<ActionToaster> -) => { - submitTelemetry(job, enable); - - if (!job.isInstalled) { - dispatch({ type: 'loading' }); - try { - await setupMlJob({ - configTemplate: job.moduleId, - indexPatternName: job.defaultIndexPattern, - jobIdErrorFilter: [job.id], - groups: job.groups, - }); - dispatch({ type: 'success' }); - } catch (error) { - errorToToaster({ title: i18n.CREATE_JOB_FAILURE, error, dispatchToaster }); - dispatch({ type: 'failure' }); - dispatch({ type: 'refresh' }); - return; - } - } - - // Max start time for job is no more than two weeks ago to ensure job performance - const maxStartTime = moment.utc().subtract(14, 'days').valueOf(); - - if (enable) { - const startTime = Math.max(latestTimestampMs, maxStartTime); - try { - await startDatafeeds({ datafeedIds: [`datafeed-${job.id}`], start: startTime }); - } catch (error) { - track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_ENABLE_FAILURE); - errorToToaster({ title: i18n.START_JOB_FAILURE, error, dispatchToaster }); - } - } else { - try { - await stopDatafeeds({ datafeedIds: [`datafeed-${job.id}`] }); - } catch (error) { - track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_DISABLE_FAILURE); - errorToToaster({ title: i18n.STOP_JOB_FAILURE, error, dispatchToaster }); - } - } - dispatch({ type: 'refresh' }); -}; - -const submitTelemetry = (job: SecurityJob, enabled: boolean) => { - // Report type of job enabled/disabled - track( - METRIC_TYPE.COUNT, - job.isElasticJob - ? enabled - ? TELEMETRY_EVENT.SIEM_JOB_ENABLED - : TELEMETRY_EVENT.SIEM_JOB_DISABLED - : enabled - ? TELEMETRY_EVENT.CUSTOM_JOB_ENABLED - : TELEMETRY_EVENT.CUSTOM_JOB_DISABLED - ); -}; - MlPopover.displayName = 'MlPopover'; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/translations.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/translations.ts index 08df99d5cebeb..960734936c1dd 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/translations.ts @@ -39,26 +39,12 @@ export const MODULE_NOT_COMPATIBLE_TITLE = (incompatibleJobCount: number) => i18n.translate('xpack.securitySolution.components.mlPopup.moduleNotCompatibleTitle', { values: { incompatibleJobCount }, defaultMessage: - '{incompatibleJobCount} {incompatibleJobCount, plural, =1 {job} other {jobs}} are currently unavailable', + '{incompatibleJobCount} {incompatibleJobCount, plural, =1 {job is} other {jobs are}} currently unavailable', }); -export const START_JOB_FAILURE = i18n.translate( - 'xpack.securitySolution.components.mlPopup.errors.startJobFailureTitle', +export const ANOMALY_DETECTION_DOCS = i18n.translate( + 'xpack.securitySolution.entityAnalytics.anomalies.AnomalyDetectionDocsTitle', { - defaultMessage: 'Start job failure', - } -); - -export const STOP_JOB_FAILURE = i18n.translate( - 'xpack.securitySolution.containers.errors.stopJobFailureTitle', - { - defaultMessage: 'Stop job failure', - } -); - -export const CREATE_JOB_FAILURE = i18n.translate( - 'xpack.securitySolution.components.mlPopup.errors.createJobFailureTitle', - { - defaultMessage: 'Create job failure', + defaultMessage: 'Anomaly Detection with Machine Learning', } ); diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx index 857b4b11b4d88..dcd152bca45b6 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React, { useMemo, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; import type { Filter } from '@kbn/es-query'; import { EVENT_ACTION } from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names'; import { ENTRY_SESSION_ENTITY_ID_PROPERTY, EventAction } from '@kbn/session-view-plugin/public'; @@ -21,6 +22,7 @@ import { getDefaultControlColumn } from '../../../timelines/components/timeline/ import { useLicense } from '../../hooks/use_license'; import { TableId } from '../../../../common/types/timeline'; export const TEST_ID = 'security_solution:sessions_viewer:sessions_view'; +import { dataTableActions } from '../../store/data_table'; export const defaultSessionsFilter: Required<Pick<Filter, 'meta' | 'query'>> = { query: { @@ -102,6 +104,17 @@ const SessionsViewComponent: React.FC<SessionsComponentsProps> = ({ const unit = (c: number) => c > 1 ? i18n.TOTAL_COUNT_OF_SESSIONS : i18n.SINGLE_COUNT_OF_SESSIONS; + const dispatch = useDispatch(); + + useEffect(() => { + dispatch( + dataTableActions.initializeTGridSettings({ + id: tableId, + title: i18n.SESSIONS_TITLE, + }) + ); + }, [dispatch, tableId]); + return ( <div data-test-subj={TEST_ID}> <StatefulEventsViewer diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts index ea35892f3a2f9..15e12212bb998 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/translations.ts @@ -7,6 +7,10 @@ import { i18n } from '@kbn/i18n'; +export const SESSIONS_TITLE = i18n.translate('xpack.securitySolution.sessionsView.sessionsTitle', { + defaultMessage: 'Sessions', +}); + export const TOTAL_COUNT_OF_SESSIONS = i18n.translate( 'xpack.securitySolution.sessionsView.totalCountOfSessions', { diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx index 6712782b5d7b1..209c0a5ace404 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.test.tsx @@ -21,11 +21,11 @@ import { } from './helpers'; import { SourcererScopeName } from '../../store/sourcerer/model'; -/** the following `TimelineId`s are detection alert tables */ -const detectionAlertsTimelines = [TableId.alertsOnAlertsPage, TableId.alertsOnRuleDetailsPage]; +/** the following scopes are detection alert tables */ +const detectionAlertsTables = [TableId.alertsOnAlertsPage, TableId.alertsOnRuleDetailsPage]; -/** the following `TimelineId`s are NOT detection alert tables */ -const otherTimelines = [ +/** the following scopes are NOT detection alert tables */ +const otherScopes = [ TableId.hostsPageEvents, TableId.hostsPageSessions, TableId.networkPageEvents, @@ -36,7 +36,7 @@ const otherTimelines = [ TableId.kubernetesPageSessions, ]; -const othersWithoutActive = otherTimelines.filter((x) => x !== TimelineId.active); +const othersWithoutActive = otherScopes.filter((x) => x !== TimelineId.active); const hostNameFilter: Filter = { meta: { @@ -169,13 +169,13 @@ describe('getOptions', () => { }); describe('isDetectionsAlertsTable', () => { - detectionAlertsTimelines.forEach((tableId) => + detectionAlertsTables.forEach((tableId) => test(`it returns true for detections alerts table '${tableId}'`, () => { expect(isDetectionsAlertsTable(tableId)).toEqual(true); }) ); - otherTimelines.forEach((tableId) => + otherScopes.forEach((tableId) => test(`it returns false for (NON alert table) timeline '${tableId}'`, () => { expect(isDetectionsAlertsTable(tableId)).toEqual(false); }) @@ -183,7 +183,7 @@ describe('isDetectionsAlertsTable', () => { }); describe('shouldIgnoreAlertFilters', () => { - detectionAlertsTimelines.forEach((tableId) => { + detectionAlertsTables.forEach((tableId) => { test(`it returns true when the view is 'raw' for detections alerts table '${tableId}'`, () => { const view = 'raw'; expect(shouldIgnoreAlertFilters({ tableId, view })).toEqual(true); @@ -195,7 +195,7 @@ describe('shouldIgnoreAlertFilters', () => { }); }); - otherTimelines.forEach((tableId) => { + otherScopes.forEach((tableId) => { test(`it returns false when the view is 'raw' for (NON alert table) timeline'${tableId}'`, () => { const view = 'raw'; expect(shouldIgnoreAlertFilters({ tableId, view })).toEqual(false); @@ -209,7 +209,7 @@ describe('shouldIgnoreAlertFilters', () => { }); describe('removeIgnoredAlertFilters', () => { - detectionAlertsTimelines.forEach((tableId) => { + detectionAlertsTables.forEach((tableId) => { test(`it removes the ignored alert filters when the view is 'raw' for detections alerts table '${tableId}'`, () => { const view = 'raw'; expect(removeIgnoredAlertFilters({ filters: allFilters, tableId, view })).toEqual([ @@ -223,7 +223,7 @@ describe('removeIgnoredAlertFilters', () => { }); }); - otherTimelines.forEach((tableId) => { + otherScopes.forEach((tableId) => { test(`it does NOT remove any filters when the view is 'raw' for (NON alert table) '${tableId}'`, () => { const view = 'alert'; expect(removeIgnoredAlertFilters({ filters: allFilters, tableId, view })).toEqual(allFilters); @@ -237,7 +237,7 @@ describe('removeIgnoredAlertFilters', () => { }); describe('getSourcererScopeName', () => { - detectionAlertsTimelines.forEach((tableId) => { + detectionAlertsTables.forEach((tableId) => { test(`it returns the 'default' SourcererScopeName when the view is 'raw' for detections alerts table '${tableId}'`, () => { const view = 'raw'; expect(getSourcererScopeName({ scopeId: tableId, view })).toEqual(SourcererScopeName.default); diff --git a/x-pack/plugins/security_solution/public/common/components/top_risk_score_contributors/index.tsx b/x-pack/plugins/security_solution/public/common/components/top_risk_score_contributors/index.tsx index 7f59912829438..eaf8f9f35cc66 100644 --- a/x-pack/plugins/security_solution/public/common/components/top_risk_score_contributors/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/top_risk_score_contributors/index.tsx @@ -16,7 +16,7 @@ import * as i18n from './translations'; import type { RuleRisk } from '../../../../common/search_strategy'; -import { RuleLink } from '../../../detections/pages/detection_engine/rules/all/use_columns'; +import { RuleLink } from '../../../detection_engine/rule_management_ui/components/rules_table/use_columns'; export interface TopRiskScoreContributorsProps { loading: boolean; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap index f683d2828bae3..84d29e3e7fd9d 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap @@ -169,17 +169,11 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "host.name", - }, - }, - ], + "exists": Object { + "field": "host.name", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap index 425f545129ee6..d53499bd57c8a 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap @@ -149,17 +149,11 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "host.name", - }, - }, - ], + "exists": Object { + "field": "host.name", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/event.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/event.test.ts.snap index 129a82aa1692c..b5a707a8bc4bd 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/event.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/event.test.ts.snap @@ -118,17 +118,11 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "host.name", - }, - }, - ], + "exists": Object { + "field": "host.name", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_area.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_area.test.ts.snap index b9165ea5c38a8..c8de37bfbd678 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_area.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_area.test.ts.snap @@ -97,17 +97,11 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "host.name", - }, - }, - ], + "exists": Object { + "field": "host.name", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_metric.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_metric.test.ts.snap index ea37bec0d1976..e4328cb0178e2 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_metric.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_host_metric.test.ts.snap @@ -85,17 +85,11 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "host.name", - }, - }, - ], + "exists": Object { + "field": "host.name", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap index f45cd86c70ed2..18d0e8f86d936 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_area.test.ts.snap @@ -131,17 +131,11 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "host.name", - }, - }, - ], + "exists": Object { + "field": "host.name", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap index 0a66d46f9a7db..ea60bad9c8efd 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_bar.test.ts.snap @@ -144,17 +144,11 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "host.name", - }, - }, - ], + "exists": Object { + "field": "host.name", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_destination_metric.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_destination_metric.test.ts.snap index 3ca56d9f020b2..48eff6c978975 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_destination_metric.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_destination_metric.test.ts.snap @@ -85,17 +85,11 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "host.name", - }, - }, - ], + "exists": Object { + "field": "host.name", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_source_metric.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_source_metric.test.ts.snap index 2972842a6f419..31f9e00156dd1 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_source_metric.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/hosts/__snapshots__/kpi_unique_ips_source_metric.test.ts.snap @@ -85,17 +85,11 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "host.name", - }, - }, - ], + "exists": Object { + "field": "host.name", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap index 392d68b512b41..975d82eff3955 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap @@ -124,36 +124,16 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "source.ip", - }, - }, - ], - }, - }, - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "destination.ip", - }, - }, - ], - }, - }, - ], + "exists": Object { + "field": "destination.ip", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap index 173a1229e1282..34bc6f13f8004 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_dns_queries.test.ts.snap @@ -111,36 +111,16 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "source.ip", - }, - }, - ], - }, - }, - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "destination.ip", - }, - }, - ], - }, - }, - ], + "exists": Object { + "field": "source.ip", + }, + }, + Object { + "exists": Object { + "field": "destination.ip", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap index c9b0441a25a4b..b29761bbf8a74 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_network_events.test.ts.snap @@ -116,36 +116,16 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "source.ip", - }, - }, - ], - }, - }, - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "destination.ip", - }, - }, - ], - }, - }, - ], + "exists": Object { + "field": "destination.ip", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap index 532d81001ab06..b8ed38aa918c4 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_tls_handshakes.test.ts.snap @@ -135,36 +135,16 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "source.ip", - }, - }, - ], - }, - }, - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "destination.ip", - }, - }, - ], - }, - }, - ], + "exists": Object { + "field": "source.ip", + }, + }, + Object { + "exists": Object { + "field": "destination.ip", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap index 60a52f4f5b4a9..06daf745e0345 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_flow_ids.test.ts.snap @@ -99,36 +99,16 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "source.ip", - }, - }, - ], - }, - }, - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "destination.ip", - }, - }, - ], - }, - }, - ], + "exists": Object { + "field": "source.ip", + }, + }, + Object { + "exists": Object { + "field": "destination.ip", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap index 11e3f62d0cd4c..51e91c05bd975 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_area.test.ts.snap @@ -121,36 +121,16 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "source.ip", - }, - }, - ], - }, - }, - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "destination.ip", - }, - }, - ], - }, - }, - ], + "exists": Object { + "field": "destination.ip", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap index 2a702bb87f3fd..1d842f74d89b5 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_bar.test.ts.snap @@ -134,36 +134,16 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "source.ip", - }, - }, - ], - }, - }, - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "destination.ip", - }, - }, - ], - }, - }, - ], + "exists": Object { + "field": "source.ip", + }, + }, + Object { + "exists": Object { + "field": "destination.ip", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_destination_metric.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_destination_metric.test.ts.snap index 9f205c5c23c07..60eea0dd437a8 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_destination_metric.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_destination_metric.test.ts.snap @@ -72,36 +72,16 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "source.ip", - }, - }, - ], - }, - }, - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "destination.ip", - }, - }, - ], - }, - }, - ], + "exists": Object { + "field": "destination.ip", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_source_metric.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_source_metric.test.ts.snap index b7e25a6ceb8f4..ee5bea71f89fd 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_source_metric.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/kpi_unique_private_ips_source_metric.test.ts.snap @@ -72,36 +72,16 @@ Object { }, "query": Object { "bool": Object { - "filter": Array [ + "minimum_should_match": 1, + "should": Array [ + Object { + "exists": Object { + "field": "source.ip", + }, + }, Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "source.ip", - }, - }, - ], - }, - }, - Object { - "bool": Object { - "minimum_should_match": 1, - "should": Array [ - Object { - "exists": Object { - "field": "destination.ip", - }, - }, - ], - }, - }, - ], + "exists": Object { + "field": "destination.ip", }, }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap index 1dcaab239de8e..e6560a8f2c33d 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/__snapshots__/kpi_user_authentications_area.test.ts.snap @@ -7,17 +7,17 @@ Object { Object { "id": "security-solution-my-test", "name": "indexpattern-datasource-current-indexpattern", - "type": "{dataViewId}", + "type": "index-pattern", }, Object { "id": "security-solution-my-test", "name": "indexpattern-datasource-layer-31213ae3-905b-4e88-b987-0cccb1f3209f", - "type": "{dataViewId}", + "type": "index-pattern", }, Object { "id": "security-solution-my-test", "name": "indexpattern-datasource-layer-4590dafb-4ac7-45aa-8641-47a3ff0b817c", - "type": "{dataViewId}", + "type": "index-pattern", }, ], "state": Object { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts index da6bdf139a1ca..ce7be2aa9f369 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/users/kpi_user_authentications_area.ts @@ -183,18 +183,18 @@ export const kpiUserAuthenticationsAreaLensAttributes: LensAttributes = { }, references: [ { - type: '{dataViewId}', - id: 'security-solution-default', + type: 'index-pattern', + id: '{dataViewId}', name: 'indexpattern-datasource-current-indexpattern', }, { - type: '{dataViewId}', - id: 'security-solution-default', + type: 'index-pattern', + id: '{dataViewId}', name: 'indexpattern-datasource-layer-31213ae3-905b-4e88-b987-0cccb1f3209f', }, { - type: '{dataViewId}', - id: 'security-solution-default', + type: 'index-pattern', + id: '{dataViewId}', name: 'indexpattern-datasource-layer-4590dafb-4ac7-45aa-8641-47a3ff0b817c', }, ], diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx index 2cb8710aac45c..ec547ab44eba5 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx @@ -16,7 +16,7 @@ import { useRouteSpy } from '../../utils/route/use_route_spy'; import type { LensAttributes, GetLensAttributes } from './types'; import { getHostDetailsPageFilter, - filterNetworkExternalAlertData, + sourceOrDestinationIpExistsFilter, hostNameExistsFilter, getIndexFilters, } from './utils'; @@ -46,7 +46,7 @@ export const useLensAttributes = ({ } if (pageName === SecurityPageName.network && tabName === NetworkRouteType.events) { - return filterNetworkExternalAlertData; + return sourceOrDestinationIpExistsFilter; } return []; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts index c66a608ecd989..9fe0a83a45d76 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/utils.ts @@ -34,20 +34,14 @@ export const hostNameExistsFilter: Filter[] = [ { query: { bool: { - filter: [ + should: [ { - bool: { - should: [ - { - exists: { - field: 'host.name', - }, - }, - ], - minimum_should_match: 1, + exists: { + field: 'host.name', }, }, ], + minimum_should_match: 1, }, }, meta: { @@ -97,43 +91,23 @@ export const getNetworkDetailsPageFilter = (ipAddress?: string): Filter[] => ] : []; -export const filterNetworkExternalAlertData: Filter[] = [ +export const sourceOrDestinationIpExistsFilter: Filter[] = [ { query: { bool: { - filter: [ + should: [ { - bool: { - should: [ - { - bool: { - should: [ - { - exists: { - field: 'source.ip', - }, - }, - ], - minimum_should_match: 1, - }, - }, - { - bool: { - should: [ - { - exists: { - field: 'destination.ip', - }, - }, - ], - minimum_should_match: 1, - }, - }, - ], - minimum_should_match: 1, + exists: { + field: 'source.ip', + }, + }, + { + exists: { + field: 'destination.ip', }, }, ], + minimum_should_match: 1, }, }, meta: { diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.test.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.test.ts index bb65e5def2792..dba14afd0ff2e 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.test.ts @@ -10,8 +10,16 @@ import { useKibana } from '../../lib/kibana'; import { useMatrixHistogram, useMatrixHistogramCombined } from '.'; import { MatrixHistogramType } from '../../../../common/search_strategy'; import { TestProviders } from '../../mock/test_providers'; +import { useTrackHttpRequest } from '../../lib/apm/use_track_http_request'; jest.mock('../../lib/kibana'); +jest.mock('../../lib/apm/use_track_http_request'); + +const mockEndTracking = jest.fn(); +const mockStartTracking = jest.fn(() => ({ endTracking: mockEndTracking })); +(useTrackHttpRequest as jest.Mock).mockReturnValue({ + startTracking: mockStartTracking, +}); const basicResponse = { isPartial: false, @@ -42,7 +50,7 @@ describe('useMatrixHistogram', () => { }; afterEach(() => { - (useKibana().services.data.search.search as jest.Mock).mockClear(); + jest.clearAllMocks(); }); it('should update request when props has changed', async () => { @@ -156,6 +164,58 @@ describe('useMatrixHistogram', () => { act(() => rerender()); expect(abortSpy).toHaveBeenCalledTimes(3); }); + + describe('trackHttpRequest', () => { + it('should start tracking when request starts', () => { + renderHook(useMatrixHistogram, { + initialProps: props, + wrapper: TestProviders, + }); + + expect(mockStartTracking).toHaveBeenCalledWith({ + name: `securitySolutionUI matrixHistogram ${MatrixHistogramType.events}`, + }); + }); + + it('should end tracking success when the request succeeds', () => { + (useKibana().services.data.search.search as jest.Mock).mockReturnValueOnce({ + subscribe: ({ next }: { next: Function }) => next(basicResponse), + }); + + renderHook(useMatrixHistogram, { + initialProps: props, + wrapper: TestProviders, + }); + + expect(mockEndTracking).toHaveBeenCalledWith('success'); + }); + + it('should end tracking error when the partial request is invalid', () => { + (useKibana().services.data.search.search as jest.Mock).mockReturnValueOnce({ + subscribe: ({ next }: { next: Function }) => next(null), + }); + + renderHook(useMatrixHistogram, { + initialProps: props, + wrapper: TestProviders, + }); + + expect(mockEndTracking).toHaveBeenCalledWith('invalid'); + }); + + it('should end tracking error when the request fails', () => { + (useKibana().services.data.search.search as jest.Mock).mockReturnValueOnce({ + subscribe: ({ error }: { error: Function }) => error('some error'), + }); + + renderHook(useMatrixHistogram, { + initialProps: props, + wrapper: TestProviders, + }); + + expect(mockEndTracking).toHaveBeenCalledWith('error'); + }); + }); }); describe('useMatrixHistogramCombined', () => { diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index c770713b602b7..85512855580c3 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -28,6 +28,8 @@ import { getInspectResponse } from '../../../helpers'; import type { InspectResponse } from '../../../types'; import * as i18n from './translations'; import { useAppToasts } from '../../hooks/use_app_toasts'; +import { useTrackHttpRequest } from '../../lib/apm/use_track_http_request'; +import { APP_UI_ID } from '../../../../common/constants'; export type Buckets = Array<{ key: string; @@ -71,6 +73,7 @@ export const useMatrixHistogram = ({ const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); + const { startTracking } = useTrackHttpRequest(); const [matrixHistogramRequest, setMatrixHistogramRequest] = useState<MatrixHistogramRequestOptions>({ @@ -102,11 +105,14 @@ export const useMatrixHistogram = ({ buckets: [], }); - const hostsSearch = useCallback( + const search = useCallback( (request: MatrixHistogramRequestOptions) => { const asyncSearch = async () => { abortCtrl.current = new AbortController(); setLoading(true); + const { endTracking } = startTracking({ + name: `${APP_UI_ID} matrixHistogram ${histogramType}`, + }); searchSubscription$.current = data.search .search<MatrixHistogramRequestOptions, MatrixHistogramStrategyResponse>(request, { @@ -130,10 +136,12 @@ export const useMatrixHistogram = ({ totalCount: histogramBuckets.reduce((acc, bucket) => bucket.doc_count + acc, 0), buckets: histogramBuckets, })); + endTracking('success'); searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); addWarning(i18n.ERROR_MATRIX_HISTOGRAM); + endTracking('invalid'); searchSubscription$.current.unsubscribe(); } }, @@ -142,6 +150,7 @@ export const useMatrixHistogram = ({ addError(msg, { title: errorMessage ?? i18n.FAIL_MATRIX_HISTOGRAM, }); + endTracking('error'); searchSubscription$.current.unsubscribe(); }, }); @@ -151,7 +160,7 @@ export const useMatrixHistogram = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, errorMessage, addError, addWarning, histogramType] + [data.search, histogramType, addWarning, addError, errorMessage, startTracking] ); useEffect(() => { @@ -189,13 +198,13 @@ export const useMatrixHistogram = ({ useEffect(() => { // We want to search if it is not skipped, stackByField ends with ip and include missing data if (!skip) { - hostsSearch(matrixHistogramRequest); + search(matrixHistogramRequest); } return () => { searchSubscription$.current.unsubscribe(); abortCtrl.current.abort(); }; - }, [matrixHistogramRequest, hostsSearch, skip]); + }, [matrixHistogramRequest, search, skip]); useEffect(() => { if (skip) { @@ -207,7 +216,7 @@ export const useMatrixHistogram = ({ const runMatrixHistogramSearch = useCallback( (to: string, from: string) => { - hostsSearch({ + search({ ...matrixHistogramRequest, timerange: { interval: '12h', @@ -216,7 +225,7 @@ export const useMatrixHistogram = ({ }, }); }, - [matrixHistogramRequest, hostsSearch] + [matrixHistogramRequest, search] ); return [loading, matrixHistogramResponse, runMatrixHistogramSearch]; diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_invalid_filter_query.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_invalid_filter_query.tsx index a0cb9f941783e..04165a66154e0 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_invalid_filter_query.tsx +++ b/x-pack/plugins/security_solution/public/common/hooks/use_invalid_filter_query.tsx @@ -36,10 +36,13 @@ export const useInvalidFilterQuery = ({ const getErrorsSelector = useMemo(() => appSelectors.errorsSelector(), []); const errors = useDeepEqualSelector(getErrorsSelector); + const name = kqlError?.name; + const message = kqlError?.message; + useEffect(() => { - if (filterQuery === undefined && kqlError != null) { + if (!filterQuery && message && name) { // Local util for creating an replicatable error hash - const hashCode = kqlError.message + const hashCode = message .split('') // eslint-disable-next-line no-bitwise .reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0) @@ -48,14 +51,12 @@ export const useInvalidFilterQuery = ({ appActions.addErrorHash({ id, hash: hashCode, - title: kqlError.name, - message: [kqlError.message], + title: name, + message: [message], }) ); } - // This disable is required to only trigger the toast once per render - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [id, filterQuery, addError, query, startDate, endDate]); + }, [id, filterQuery, addError, query, startDate, endDate, dispatch, message, name]); useEffect(() => { const myError = errors.find((e) => e.id === id); 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 967eca53bce1b..20191f7ce312a 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 @@ -346,24 +346,26 @@ export const mockGlobalState: State = { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z', }, - sessionViewConfig: null, - show: false, + resolveTimelineConfig: undefined, pinnedEventIds: {}, pinnedEventsSaveObject: {}, - itemsPerPageOptions: [5, 10, 20], + sessionViewConfig: null, + show: false, sort: [ { columnId: '@timestamp', columnType: 'date', esTypes: ['date'], - sortDirection: Direction.desc, + sortDirection: 'desc', }, ], - isSaving: false, + status: TimelineStatus.draft, version: null, - status: TimelineStatus.active, - isSelectAllChecked: false, selectedEventIds: {}, + isSelectAllChecked: false, + filters: [], + isSaving: false, + itemsPerPageOptions: [10, 25, 50, 100], }, }, insertTimeline: null, diff --git a/x-pack/plugins/security_solution/public/common/utils/privileges/index.test.ts b/x-pack/plugins/security_solution/public/common/utils/privileges/index.test.ts new file mode 100644 index 0000000000000..34abe0dd52c9a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/utils/privileges/index.test.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 { hasUserCRUDPermission } from '.'; + +describe('privileges utils', () => { + describe('hasUserCRUDPermission', () => { + test("returns true when user's CRUD operations are null", () => { + const result = hasUserCRUDPermission(null); + + expect(result).toBeTruthy(); + }); + + test('returns false when user cannot CRUD', () => { + const result = hasUserCRUDPermission(false); + + expect(result).toBeFalsy(); + }); + + test('returns true when user can CRUD', () => { + const result = hasUserCRUDPermission(true); + + expect(result).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/utils/privileges/index.ts b/x-pack/plugins/security_solution/public/common/utils/privileges/index.ts index de8ad087f27e6..817bbc5f5f296 100644 --- a/x-pack/plugins/security_solution/public/common/utils/privileges/index.ts +++ b/x-pack/plugins/security_solution/public/common/utils/privileges/index.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { Rule } from '../../../detections/containers/detection_engine/rules'; -import * as i18n from '../../../detections/pages/detection_engine/rules/translations'; +import type { Rule } from '../../../detection_engine/rule_management/logic'; +import * as i18nActions from '../../../detections/pages/detection_engine/rules/translations'; import { isMlRule } from '../../../../common/machine_learning/helpers'; import * as detectionI18n from '../../../detections/pages/detection_engine/translations'; @@ -29,21 +29,28 @@ export const canEditRuleWithActions = ( return true; }; -export const getToolTipContent = ( +// typed as null not undefined as the initial state for this value is null. +export const hasUserCRUDPermission = (canUserCRUD: boolean | null): boolean => + canUserCRUD != null ? canUserCRUD : true; + +export const explainLackOfPermission = ( rule: Rule | null | undefined, hasMlPermissions: boolean, hasReadActionsPrivileges: | boolean | Readonly<{ [x: string]: boolean; - }> + }>, + canUserCRUD: boolean | null ): string | undefined => { if (rule == null) { return undefined; } else if (isMlRule(rule.type) && !hasMlPermissions) { return detectionI18n.ML_RULES_DISABLED_MESSAGE; } else if (!canEditRuleWithActions(rule, hasReadActionsPrivileges)) { - return i18n.EDIT_RULE_SETTINGS_TOOLTIP; + return i18nActions.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES; + } else if (!hasUserCRUDPermission(canUserCRUD)) { + return i18nActions.LACK_OF_KIBANA_SECURITY_PRIVILEGES; } else { return undefined; } diff --git a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/__mocks__/api_client.ts b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/__mocks__/api_client.ts new file mode 100644 index 0000000000000..e2d98118c593e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/__mocks__/api_client.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 type { GetInstalledIntegrationsResponse } from '../../../../../common/detection_engine/fleet_integrations'; +import type { + FetchInstalledIntegrationsArgs, + IFleetIntegrationsApiClient, +} from '../api_client_interface'; + +export const fleetIntegrationsApi: jest.Mocked<IFleetIntegrationsApiClient> = { + fetchInstalledIntegrations: jest + .fn<Promise<GetInstalledIntegrationsResponse>, [FetchInstalledIntegrationsArgs]>() + .mockResolvedValue({ + installed_integrations: [ + { + package_name: 'atlassian_bitbucket', + package_title: 'Atlassian Bitbucket', + package_version: '1.0.1', + integration_name: 'audit', + integration_title: 'Audit Logs', + is_enabled: true, + }, + { + package_name: 'system', + package_title: 'System', + package_version: '1.6.4', + is_enabled: true, + }, + ], + }), +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/__mocks__/index.ts b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/__mocks__/index.ts new file mode 100644 index 0000000000000..ebfcff4941a99 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/__mocks__/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 * from './api_client'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client.ts b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client.ts new file mode 100644 index 0000000000000..58cd7b17a1fb5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client.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. + */ + +import type { GetInstalledIntegrationsResponse } from '../../../../common/detection_engine/fleet_integrations'; +import { GET_INSTALLED_INTEGRATIONS_URL } from '../../../../common/detection_engine/fleet_integrations'; +import { KibanaServices } from '../../../common/lib/kibana'; + +import type { + FetchInstalledIntegrationsArgs, + IFleetIntegrationsApiClient, +} from './api_client_interface'; + +export const fleetIntegrationsApi: IFleetIntegrationsApiClient = { + fetchInstalledIntegrations: ( + args: FetchInstalledIntegrationsArgs + ): Promise<GetInstalledIntegrationsResponse> => { + const { packages, signal } = args; + + return http().fetch<GetInstalledIntegrationsResponse>(GET_INSTALLED_INTEGRATIONS_URL, { + method: 'GET', + query: { + packages: packages?.sort()?.join(','), + }, + signal, + }); + }, +}; + +const http = () => KibanaServices.get().http; diff --git a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client_interface.ts b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client_interface.ts new file mode 100644 index 0000000000000..4776b6e5528af --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/api_client_interface.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 type { GetInstalledIntegrationsResponse } from '../../../../common/detection_engine/fleet_integrations'; + +export interface IFleetIntegrationsApiClient { + /** + * Fetch all installed integrations. + * @throws An error if response is not OK + */ + fetchInstalledIntegrations( + args: FetchInstalledIntegrationsArgs + ): Promise<GetInstalledIntegrationsResponse>; +} + +export interface FetchInstalledIntegrationsArgs { + /** + * Array of Fleet packages to filter for. + */ + packages?: string[]; + + /** + * Optional signal for cancelling the request. + */ + signal?: AbortSignal; +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/index.ts b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/index.ts new file mode 100644 index 0000000000000..f20ead0b41939 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/api/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 * from './api_client_interface'; +export * from './api_client'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/index.ts b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/index.ts new file mode 100644 index 0000000000000..473b51dc47c28 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/fleet_integrations/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 * from './api'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts similarity index 97% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts index 2ab0c7ae0b5b2..af6a82af1a9d5 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts @@ -6,12 +6,12 @@ */ import type { List } from '@kbn/securitysolution-io-ts-list-types'; -import type { CreateRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; -import type { Rule } from '../../../../containers/detection_engine/rules'; +import type { RuleCreateProps } from '../../../../../common/detection_engine/rule_schema'; +import type { Rule } from '../../../rule_management/logic'; import { getListMock, getEndpointListMock, -} from '../../../../../../common/detection_engine/schemas/types/lists.mock'; +} from '../../../../../common/detection_engine/schemas/types/lists.mock'; import type { DefineStepRuleJson, ScheduleStepRuleJson, @@ -21,7 +21,7 @@ import type { ActionsStepRule, ScheduleStepRule, DefineStepRule, -} from '../types'; +} from '../../../../detections/pages/detection_engine/rules/types'; import { getTimeTypeValue, formatDefineStepData, @@ -38,8 +38,8 @@ import { mockScheduleStepRule, mockAboutStepRule, mockActionsStepRule, -} from '../all/__mocks__/mock'; -import { getThreatMock } from '../../../../../../common/detection_engine/schemas/types/threat.mock'; +} from '../../../rule_management_ui/components/rules_table/__mocks__/mock'; +import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; import type { Threat, Threats } from '@kbn/securitysolution-io-ts-alerting-types'; describe('helpers', () => { @@ -958,7 +958,7 @@ describe('helpers', () => { saved_id: '', }, }; - const result = formatRule<CreateRulesSchema>( + const result = formatRule<RuleCreateProps>( mockDefineStepRuleWithoutSavedId, mockAbout, mockSchedule, @@ -969,14 +969,9 @@ describe('helpers', () => { }); test('returns rule without id if ruleId does not exist', () => { - const result = formatRule<CreateRulesSchema>( - mockDefine, - mockAbout, - mockSchedule, - mockActions - ); + const result = formatRule<RuleCreateProps>(mockDefine, mockAbout, mockSchedule, mockActions); - expect(result).not.toHaveProperty<CreateRulesSchema>('id'); + expect(result).not.toHaveProperty<RuleCreateProps>('id'); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts similarity index 96% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts index 9beeb17d3a1bb..6686239e053f9 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts @@ -23,12 +23,12 @@ import type { Type, } from '@kbn/securitysolution-io-ts-alerting-types'; import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; -import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../../common/constants'; -import { assertUnreachable } from '../../../../../../common/utility_types'; +import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../common/constants'; +import { assertUnreachable } from '../../../../../common/utility_types'; import { transformAlertToRuleAction, transformAlertToRuleResponseAction, -} from '../../../../../../common/detection_engine/transform_actions'; +} from '../../../../../common/detection_engine/transform_actions'; import type { AboutStepRule, @@ -41,10 +41,10 @@ import type { ActionsStepRuleJson, RuleStepsFormData, RuleStep, -} from '../types'; -import { DataSourceType } from '../types'; -import type { CreateRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; -import { stepActionsDefaultValue } from '../../../../components/rules/step_rule_actions'; +} from '../../../../detections/pages/detection_engine/rules/types'; +import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types'; +import type { RuleCreateProps } from '../../../../../common/detection_engine/rule_schema'; +import { stepActionsDefaultValue } from '../../../../detections/components/rules/step_rule_actions'; export const getTimeTypeValue = (time: string): { unit: Unit; value: number } => { const timeObj: { unit: Unit; value: number } = { @@ -568,7 +568,7 @@ export const formatActionsStepData = (actionsStepData: ActionsStepRule): Actions // Used to format form data in rule edit and // create flows so "T" here would likely -// either be CreateRulesSchema or Rule +// either be RuleCreateProps or Rule export const formatRule = <T>( defineStepData: DefineStepRule, aboutStepData: AboutStepRule, @@ -593,14 +593,14 @@ export const formatPreviewRule = ({ aboutRuleData: AboutStepRule; scheduleRuleData: ScheduleStepRule; exceptionsList?: List[]; -}): CreateRulesSchema => { +}): RuleCreateProps => { const aboutStepData = { ...aboutRuleData, name: 'Preview Rule', description: 'Preview Rule', }; return { - ...formatRule<CreateRulesSchema>( + ...formatRule<RuleCreateProps>( defineRuleData, aboutStepData, scheduleRuleData, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx similarity index 88% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx index 0a4914274d2e6..a5dbc91d1709b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/index.tsx @@ -18,32 +18,32 @@ import React, { useCallback, useRef, useState, useMemo, useEffect } from 'react' import styled from 'styled-components'; import type { DataViewListItem } from '@kbn/data-views-plugin/common'; -import { isThreatMatchRule } from '../../../../../../common/detection_engine/utils'; -import { useCreateRule } from '../../../../containers/detection_engine/rules'; -import type { CreateRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; -import { useListsConfig } from '../../../../containers/detection_engine/lists/use_lists_config'; +import { isThreatMatchRule } from '../../../../../common/detection_engine/utils'; +import { useCreateRule } from '../../../rule_management/logic'; +import type { RuleCreateProps } from '../../../../../common/detection_engine/rule_schema'; +import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; +import { hasUserCRUDPermission } from '../../../../common/utils/privileges'; import { getDetectionEngineUrl, getRuleDetailsUrl, getRulesUrl, -} from '../../../../../common/components/link_to/redirect_to_detection_engine'; -import { SecuritySolutionPageWrapper } from '../../../../../common/components/page_wrapper'; -import { displaySuccessToast, useStateToaster } from '../../../../../common/components/toasters'; -import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; -import { useUserData } from '../../../../components/user_info'; -import { AccordionTitle } from '../../../../components/rules/accordion_title'; -import { StepDefineRule } from '../../../../components/rules/step_define_rule'; -import { StepAboutRule } from '../../../../components/rules/step_about_rule'; -import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule'; -import { StepRuleActions } from '../../../../components/rules/step_rule_actions'; -import * as RuleI18n from '../translations'; +} from '../../../../common/components/link_to/redirect_to_detection_engine'; +import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper'; +import { displaySuccessToast, useStateToaster } from '../../../../common/components/toasters'; +import { SpyRoute } from '../../../../common/utils/route/spy_routes'; +import { useUserData } from '../../../../detections/components/user_info'; +import { AccordionTitle } from '../../../../detections/components/rules/accordion_title'; +import { StepDefineRule } from '../../../../detections/components/rules/step_define_rule'; +import { StepAboutRule } from '../../../../detections/components/rules/step_about_rule'; +import { StepScheduleRule } from '../../../../detections/components/rules/step_schedule_rule'; +import { StepRuleActions } from '../../../../detections/components/rules/step_rule_actions'; +import * as RuleI18n from '../../../../detections/pages/detection_engine/rules/translations'; import { redirectToDetections, getActionMessageParams, - userHasPermissions, MaxWidthEuiFlexItem, -} from '../helpers'; +} from '../../../../detections/pages/detection_engine/rules/helpers'; import type { AboutStepRule, DefineStepRule, @@ -51,26 +51,26 @@ import type { RuleStepsFormData, RuleStepsFormHooks, RuleStepsData, -} from '../types'; -import { RuleStep } from '../types'; +} from '../../../../detections/pages/detection_engine/rules/types'; +import { RuleStep } from '../../../../detections/pages/detection_engine/rules/types'; import { formatRule, stepIsValid } from './helpers'; import * as i18n from './translations'; -import { SecurityPageName } from '../../../../../app/types'; +import { SecurityPageName } from '../../../../app/types'; import { getStepScheduleDefaultValue, ruleStepsOrder, stepAboutDefaultValue, stepDefineDefaultValue, -} from '../utils'; +} from '../../../../detections/pages/detection_engine/rules/utils'; import { APP_UI_ID, DEFAULT_INDEX_KEY, DEFAULT_INDICATOR_SOURCE_PATH, DEFAULT_THREAT_INDEX_KEY, -} from '../../../../../../common/constants'; -import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; -import { HeaderPage } from '../../../../../common/components/header_page'; -import { PreviewFlyout } from '../preview'; +} from '../../../../../common/constants'; +import { useKibana, useUiSetting$ } from '../../../../common/lib/kibana'; +import { HeaderPage } from '../../../../common/components/header_page'; +import { PreviewFlyout } from '../../../../detections/pages/detection_engine/rules/preview'; const formHookNoop = async (): Promise<undefined> => undefined; @@ -163,9 +163,8 @@ const CreateRulePageComponent: React.FC = () => { [RuleStep.scheduleRule]: false, [RuleStep.ruleActions]: false, }); - const [{ isLoading, ruleId }, setRule] = useCreateRule(); + const { mutateAsync: createRule, isLoading } = useCreateRule(); const ruleType = stepsData.current[RuleStep.defineRule].data?.ruleType; - const ruleName = stepsData.current[RuleStep.aboutRule].data?.name; const actionMessageParams = useMemo(() => getActionMessageParams(ruleType), [ruleType]); const [dataViewOptions, setDataViewOptions] = useState<{ [x: string]: DataViewListItem }>({}); const [isPreviewDisabled, setIsPreviewDisabled] = useState(false); @@ -286,19 +285,25 @@ const CreateRulePageComponent: React.FC = () => { stepIsValid(scheduleStep) && stepIsValid(actionsStep) ) { - setRule( - formatRule<CreateRulesSchema>( + const createdRule = await createRule( + formatRule<RuleCreateProps>( defineStep.data, aboutStep.data, scheduleStep.data, actionsStep.data ) ); + + displaySuccessToast(i18n.SUCCESSFULLY_CREATED_RULES(createdRule.name), dispatchToaster); + navigateToApp(APP_UI_ID, { + deepLinkId: SecurityPageName.rules, + path: getRuleDetailsUrl(createdRule.id), + }); } } } }, - [goToStep, setRule, updateCurrentDataState] + [updateCurrentDataState, goToStep, createRule, dispatchToaster, navigateToApp] ); const getAccordionType = useCallback( @@ -342,15 +347,6 @@ const CreateRulePageComponent: React.FC = () => { /> ); - if (ruleName && ruleId) { - displaySuccessToast(i18n.SUCCESSFULLY_CREATED_RULES(ruleName), dispatchToaster); - navigateToApp(APP_UI_ID, { - deepLinkId: SecurityPageName.rules, - path: getRuleDetailsUrl(ruleId), - }); - return null; - } - if ( redirectToDetections( isSignalIndexExists, @@ -364,7 +360,7 @@ const CreateRulePageComponent: React.FC = () => { path: getDetectionEngineUrl(), }); return null; - } else if (!userHasPermissions(canUserCRUD)) { + } else if (!hasUserCRUDPermission(canUserCRUD)) { navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRulesUrl(), diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/translations.ts diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx similarity index 86% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx index 23d96c93aea2a..78b58f95c91a0 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx @@ -21,22 +21,23 @@ import { useParams } from 'react-router-dom'; import { noop } from 'lodash'; import type { DataViewListItem } from '@kbn/data-views-plugin/common'; -import type { UpdateRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; -import { useRule, useUpdateRule } from '../../../../containers/detection_engine/rules'; -import { useListsConfig } from '../../../../containers/detection_engine/lists/use_lists_config'; -import { SecuritySolutionPageWrapper } from '../../../../../common/components/page_wrapper'; +import type { RuleUpdateProps } from '../../../../../common/detection_engine/rule_schema'; +import { useRule, useUpdateRule } from '../../../rule_management/logic'; +import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; +import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper'; +import { hasUserCRUDPermission } from '../../../../common/utils/privileges'; import { getRuleDetailsUrl, getDetectionEngineUrl, -} from '../../../../../common/components/link_to/redirect_to_detection_engine'; -import { displaySuccessToast, useStateToaster } from '../../../../../common/components/toasters'; -import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; -import { useUserData } from '../../../../components/user_info'; -import { StepPanel } from '../../../../components/rules/step_panel'; -import { StepAboutRule } from '../../../../components/rules/step_about_rule'; -import { StepDefineRule } from '../../../../components/rules/step_define_rule'; -import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule'; -import { StepRuleActions } from '../../../../components/rules/step_rule_actions'; +} from '../../../../common/components/link_to/redirect_to_detection_engine'; +import { displaySuccessToast, useStateToaster } from '../../../../common/components/toasters'; +import { SpyRoute } from '../../../../common/utils/route/spy_routes'; +import { useUserData } from '../../../../detections/components/user_info'; +import { StepPanel } from '../../../../detections/components/rules/step_panel'; +import { StepAboutRule } from '../../../../detections/components/rules/step_about_rule'; +import { StepDefineRule } from '../../../../detections/components/rules/step_define_rule'; +import { StepScheduleRule } from '../../../../detections/components/rules/step_schedule_rule'; +import { StepRuleActions } from '../../../../detections/components/rules/step_rule_actions'; import { formatRule, stepIsValid, @@ -44,15 +45,14 @@ import { isAboutStep, isScheduleStep, isActionsStep, -} from '../create/helpers'; +} from '../rule_creation/helpers'; import { getStepsData, redirectToDetections, getActionMessageParams, - userHasPermissions, MaxWidthEuiFlexItem, -} from '../helpers'; -import * as ruleI18n from '../translations'; +} from '../../../../detections/pages/detection_engine/rules/helpers'; +import * as ruleI18n from '../../../../detections/pages/detection_engine/rules/translations'; import type { ActionsStepRule, AboutStepRule, @@ -61,22 +61,22 @@ import type { RuleStepsFormHooks, RuleStepsFormData, RuleStepsData, -} from '../types'; -import { RuleStep } from '../types'; +} from '../../../../detections/pages/detection_engine/rules/types'; +import { RuleStep } from '../../../../detections/pages/detection_engine/rules/types'; import * as i18n from './translations'; -import { SecurityPageName } from '../../../../../app/types'; -import { ruleStepsOrder } from '../utils'; -import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; +import { SecurityPageName } from '../../../../app/types'; +import { ruleStepsOrder } from '../../../../detections/pages/detection_engine/rules/utils'; +import { useKibana, useUiSetting$ } from '../../../../common/lib/kibana'; import { APP_UI_ID, DEFAULT_INDEX_KEY, DEFAULT_THREAT_INDEX_KEY, -} from '../../../../../../common/constants'; -import { HeaderPage } from '../../../../../common/components/header_page'; -import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; -import { SINGLE_RULE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; -import { PreviewFlyout } from '../preview'; -import { useGetSavedQuery } from '../use_get_saved_query'; +} from '../../../../../common/constants'; +import { HeaderPage } from '../../../../common/components/header_page'; +import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; +import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; +import { PreviewFlyout } from '../../../../detections/pages/detection_engine/rules/preview'; +import { useGetSavedQuery } from '../../../../detections/pages/detection_engine/rules/use_get_saved_query'; const formHookNoop = async (): Promise<undefined> => undefined; @@ -96,8 +96,8 @@ const EditRulePageComponent: FC = () => { const { data: dataServices } = useKibana().services; const { navigateToApp } = useKibana().services.application; - const { detailName: ruleId } = useParams<{ detailName: string | undefined }>(); - const [ruleLoading, rule] = useRule(ruleId); + const { detailName: ruleId } = useParams<{ detailName: string }>(); + const { data: rule, isLoading: ruleLoading } = useRule(ruleId); const loading = ruleLoading || userInfoLoading || listsConfigLoading; const { isSavedQueryLoading, savedQueryBar, savedQuery } = useGetSavedQuery(rule?.saved_id, { @@ -126,7 +126,7 @@ const EditRulePageComponent: FC = () => { const stepData = stepsData.current[step]; return stepData.data != null && !stepIsValid(stepData); }); - const [{ isLoading, isSaved }, setRule] = useUpdateRule(); + const { mutateAsync: updateRule, isLoading } = useUpdateRule(); const [dataViewOptions, setDataViewOptions] = useState<{ [x: string]: DataViewListItem }>({}); const [isPreviewDisabled, setIsPreviewDisabled] = useState(false); const [isRulePreviewVisible, setIsRulePreviewVisible] = useState(false); @@ -358,8 +358,8 @@ const EditRulePageComponent: FC = () => { stepIsValid(actions) ) { startTransaction({ name: SINGLE_RULE_ACTIONS.SAVE }); - setRule({ - ...formatRule<UpdateRulesSchema>( + await updateRule({ + ...formatRule<RuleUpdateProps>( define.data, about.data, schedule.data, @@ -369,17 +369,25 @@ const EditRulePageComponent: FC = () => { ...(ruleId ? { id: ruleId } : {}), ...(rule != null ? { max_signals: rule.max_signals } : {}), }); + + displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster); + navigateToApp(APP_UI_ID, { + deepLinkId: SecurityPageName.rules, + path: getRuleDetailsUrl(ruleId ?? ''), + }); } }, [ aboutStep, actionsStep, activeStep, defineStep, + dispatchToaster, + navigateToApp, rule, ruleId, scheduleStep, - setRule, setStepData, + updateRule, startTransaction, ]); @@ -432,15 +440,6 @@ const EditRulePageComponent: FC = () => { } }, [rule]); - if (isSaved) { - displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster); - navigateToApp(APP_UI_ID, { - deepLinkId: SecurityPageName.rules, - path: getRuleDetailsUrl(ruleId ?? ''), - }); - return null; - } - if ( redirectToDetections( isSignalIndexExists, @@ -454,7 +453,7 @@ const EditRulePageComponent: FC = () => { path: getDetectionEngineUrl(), }); return null; - } else if (!userHasPermissions(canUserCRUD)) { + } else if (!hasUserCRUDPermission(canUserCRUD)) { navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId ?? ''), diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/translations.ts diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/__mocks__/rule_details_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/__mocks__/rule_details_context.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/__mocks__/rule_details_context.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/__mocks__/rule_details_context.tsx diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/__snapshots__/execution_log_search_bar.test.tsx.snap b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/__snapshots__/execution_log_search_bar.test.tsx.snap similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/__snapshots__/execution_log_search_bar.test.tsx.snap rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/__snapshots__/execution_log_search_bar.test.tsx.snap diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_columns.tsx similarity index 89% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_columns.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_columns.tsx index 9d5fd1a974dac..610277ddcd076 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_columns.tsx @@ -14,13 +14,13 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { RuleExecutionResult, RuleExecutionStatus, -} from '../../../../../../../common/detection_engine/rule_monitoring'; +} from '../../../../../../common/detection_engine/rule_monitoring'; -import { getEmptyValue } from '../../../../../../common/components/empty_value'; -import { FormattedDate } from '../../../../../../common/components/formatted_date'; -import { ExecutionStatusIndicator } from '../../../../../../detection_engine/rule_monitoring'; -import { PopoverTooltip } from '../../all/popover_tooltip'; -import { TableHeaderTooltipCell } from '../../all/table_header_tooltip_cell'; +import { getEmptyValue } from '../../../../../common/components/empty_value'; +import { FormattedDate } from '../../../../../common/components/formatted_date'; +import { ExecutionStatusIndicator } from '../../../../rule_monitoring'; +import { PopoverTooltip } from '../../../../rule_management_ui/components/rules_table/popover_tooltip'; +import { TableHeaderTooltipCell } from '../../../../rule_management_ui/components/rules_table/table_header_tooltip_cell'; import { RuleDurationFormat } from './rule_duration_format'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_search_bar.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_search_bar.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_search_bar.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_search_bar.test.tsx diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_search_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_search_bar.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_search_bar.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_search_bar.tsx index 39a4c3ee159de..98c61183ab861 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_search_bar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_search_bar.tsx @@ -9,8 +9,8 @@ import React, { useCallback } from 'react'; import { replace } from 'lodash'; import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { RuleExecutionStatus } from '../../../../../../../common/detection_engine/rule_monitoring'; -import { ExecutionStatusFilter } from '../../../../../../detection_engine/rule_monitoring'; +import { RuleExecutionStatus } from '../../../../../../common/detection_engine/rule_monitoring'; +import { ExecutionStatusFilter } from '../../../../rule_monitoring'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_table.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx similarity index 76% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_table.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx index 52f85b228ab36..e8cc02990289e 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_table.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.test.tsx @@ -9,19 +9,17 @@ import React from 'react'; import { noop } from 'lodash/fp'; import { render, screen } from '@testing-library/react'; -import { TestProviders } from '../../../../../../common/mock'; +import { TestProviders } from '../../../../../common/mock'; import { useRuleDetailsContextMock } from '../__mocks__/rule_details_context'; -import { getRuleExecutionResultsResponseMock } from '../../../../../../../common/detection_engine/rule_monitoring/mocks'; +import { getRuleExecutionResultsResponseMock } from '../../../../../../common/detection_engine/rule_monitoring/mocks'; -import { useExecutionResults } from '../../../../../../detection_engine/rule_monitoring'; -import { useSourcererDataView } from '../../../../../../common/containers/sourcerer'; +import { useExecutionResults } from '../../../../rule_monitoring'; +import { useSourcererDataView } from '../../../../../common/containers/sourcerer'; import { useRuleDetailsContext } from '../rule_details_context'; import { ExecutionLogTable } from './execution_log_table'; -jest.mock('../../../../../../common/containers/sourcerer'); -jest.mock( - '../../../../../../detection_engine/rule_monitoring/components/execution_results_table/use_execution_results' -); +jest.mock('../../../../../common/containers/sourcerer'); +jest.mock('../../../../rule_monitoring/components/execution_results_table/use_execution_results'); jest.mock('../rule_details_context'); const mockUseSourcererDataView = useSourcererDataView as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_table.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx index 11b8e15194d07..072d3b6f58174 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/execution_log_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/execution_log_table.tsx @@ -27,41 +27,38 @@ import { buildFilter, FILTERS } from '@kbn/es-query'; import { MAX_EXECUTION_EVENTS_DISPLAYED } from '@kbn/securitysolution-rules'; import { mountReactNode } from '@kbn/core-mount-utils-browser-internal'; -import { InputsModelId } from '../../../../../../common/store/inputs/constants'; +import { InputsModelId } from '../../../../../common/store/inputs/constants'; import { RuleDetailTabs } from '..'; -import { RULE_DETAILS_EXECUTION_LOG_TABLE_SHOW_METRIC_COLUMNS_STORAGE_KEY } from '../../../../../../../common/constants'; +import { RULE_DETAILS_EXECUTION_LOG_TABLE_SHOW_METRIC_COLUMNS_STORAGE_KEY } from '../../../../../../common/constants'; import type { RuleExecutionResult, RuleExecutionStatus, -} from '../../../../../../../common/detection_engine/rule_monitoring'; +} from '../../../../../../common/detection_engine/rule_monitoring'; -import { HeaderSection } from '../../../../../../common/components/header_section'; +import { HeaderSection } from '../../../../../common/components/header_section'; import { UtilityBar, UtilityBarGroup, UtilityBarSection, UtilityBarText, -} from '../../../../../../common/components/utility_bar'; -import { useSourcererDataView } from '../../../../../../common/containers/sourcerer'; -import { useAppToasts } from '../../../../../../common/hooks/use_app_toasts'; -import { useDeepEqualSelector } from '../../../../../../common/hooks/use_selector'; -import { useKibana } from '../../../../../../common/lib/kibana'; -import { inputsSelectors } from '../../../../../../common/store'; +} from '../../../../../common/components/utility_bar'; +import { useSourcererDataView } from '../../../../../common/containers/sourcerer'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; +import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; +import { useKibana } from '../../../../../common/lib/kibana'; +import { inputsSelectors } from '../../../../../common/store'; import { setAbsoluteRangeDatePicker, setFilterQuery, setRelativeRangeDatePicker, -} from '../../../../../../common/store/inputs/actions'; +} from '../../../../../common/store/inputs/actions'; import type { AbsoluteTimeRange, RelativeTimeRange, -} from '../../../../../../common/store/inputs/model'; -import { - isAbsoluteTimeRange, - isRelativeTimeRange, -} from '../../../../../../common/store/inputs/model'; -import { SourcererScopeName } from '../../../../../../common/store/sourcerer/model'; -import { useExecutionResults } from '../../../../../../detection_engine/rule_monitoring'; +} from '../../../../../common/store/inputs/model'; +import { isAbsoluteTimeRange, isRelativeTimeRange } from '../../../../../common/store/inputs/model'; +import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; +import { useExecutionResults } from '../../../../rule_monitoring'; import { useRuleDetailsContext } from '../rule_details_context'; import * as i18n from './translations'; import { EXECUTION_LOG_COLUMNS, GET_EXECUTION_LOG_METRICS_COLUMNS } from './execution_log_columns'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/rule_duration_format.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/rule_duration_format.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/rule_duration_format.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/rule_duration_format.test.tsx diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/rule_duration_format.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/rule_duration_format.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/rule_duration_format.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/rule_duration_format.tsx diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/execution_log_table/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/execution_log_table/translations.ts diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.test.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.test.tsx index 8130abd7efe3d..2cd73c6293a17 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.test.tsx @@ -8,64 +8,57 @@ import React from 'react'; import { mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; +import { tGridReducer } from '@kbn/timelines-plugin/public'; -import '../../../../../common/mock/match_media'; +import '../../../../common/mock/match_media'; import { createSecuritySolutionStorageMock, kibanaObservable, mockGlobalState, TestProviders, SUB_PLUGINS_REDUCER, -} from '../../../../../common/mock'; +} from '../../../../common/mock'; import { RuleDetailsPage } from '.'; -import type { State } from '../../../../../common/store'; -import { createStore } from '../../../../../common/store'; -import { useUserData } from '../../../../components/user_info'; -import { useRuleWithFallback } from '../../../../containers/detection_engine/rules/use_rule_with_fallback'; +import type { State } from '../../../../common/store'; +import { createStore } from '../../../../common/store'; +import { useUserData } from '../../../../detections/components/user_info'; +import { useRuleWithFallback } from '../../../rule_management/logic/use_rule_with_fallback'; -import { useSourcererDataView } from '../../../../../common/containers/sourcerer'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { useParams } from 'react-router-dom'; -import { mockHistory, Router } from '../../../../../common/mock/router'; +import { mockHistory, Router } from '../../../../common/mock/router'; -import { fillEmptySeverityMappings } from '../helpers'; -import { tGridReducer } from '@kbn/timelines-plugin/public'; +import { fillEmptySeverityMappings } from '../../../../detections/pages/detection_engine/rules/helpers'; // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar -jest.mock('../../../../../common/components/search_bar', () => ({ +jest.mock('../../../../common/components/search_bar', () => ({ SiemSearchBar: () => null, })); -jest.mock('../helpers', () => { - const original = jest.requireActual('../helpers'); +jest.mock('../../../../detections/pages/detection_engine/rules/helpers', () => { + const original = jest.requireActual( + '../../../../detections/pages/detection_engine/rules/helpers' + ); return { ...original, fillEmptySeverityMappings: jest.fn().mockReturnValue([]), }; }); -jest.mock('../../../../../common/components/query_bar', () => ({ +jest.mock('../../../../common/components/query_bar', () => ({ QueryBar: () => null, })); -jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); -jest.mock('../../../../../common/components/link_to'); -jest.mock('../../../../components/user_info'); -jest.mock('../../../../containers/detection_engine/rules', () => { - const original = jest.requireActual('../../../../containers/detection_engine/rules'); - return { - ...original, - useRuleStatus: jest.fn(), - }; -}); -jest.mock('../../../../containers/detection_engine/rules/use_rule_with_fallback', () => { - const original = jest.requireActual( - '../../../../containers/detection_engine/rules/use_rule_with_fallback' - ); +jest.mock('../../../../detections/containers/detection_engine/lists/use_lists_config'); +jest.mock('../../../../common/components/link_to'); +jest.mock('../../../../detections/components/user_info'); +jest.mock('../../../rule_management/logic/use_rule_with_fallback', () => { + const original = jest.requireActual('../../../rule_management/logic/use_rule_with_fallback'); return { ...original, useRuleWithFallback: jest.fn(), }; }); -jest.mock('../../../../../common/containers/sourcerer', () => { - const actual = jest.requireActual('../../../../../common/containers/sourcerer'); +jest.mock('../../../../common/containers/sourcerer', () => { + const actual = jest.requireActual('../../../../common/containers/sourcerer'); return { ...actual, useSourcererDataView: jest @@ -73,7 +66,7 @@ jest.mock('../../../../../common/containers/sourcerer', () => { .mockReturnValue({ indexPattern: ['fakeindex'], loading: false }), }; }); -jest.mock('../../../../../common/containers/use_global_time', () => ({ +jest.mock('../../../../common/containers/use_global_time', () => ({ useGlobalTime: jest.fn().mockReturnValue({ from: '2020-07-07T08:20:18.966Z', isInitializing: false, @@ -94,8 +87,8 @@ jest.mock('react-router-dom', () => { const mockRedirectLegacyUrl = jest.fn(); const mockGetLegacyUrlConflict = jest.fn(); -jest.mock('../../../../../common/lib/kibana', () => { - const originalModule = jest.requireActual('../../../../../common/lib/kibana'); +jest.mock('../../../../common/lib/kibana', () => { + const originalModule = jest.requireActual('../../../../common/lib/kibana'); return { ...originalModule, useKibana: () => ({ diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx similarity index 82% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index 2989b77ec28de..5e7bf4a7019a7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -27,111 +27,111 @@ import type { ConnectedProps } from 'react-redux'; import { connect, useDispatch } from 'react-redux'; import styled from 'styled-components'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; - import type { Dispatch } from 'redux'; import { isTab } from '@kbn/timelines-plugin/public'; import type { DataViewListItem } from '@kbn/data-views-plugin/common'; -import { tableDefaults } from '../../../../../common/store/data_table/defaults'; -import { dataTableActions, dataTableSelectors } from '../../../../../common/store/data_table'; -import { SecuritySolutionTabNavigation } from '../../../../../common/components/navigation'; -import { InputsModelId } from '../../../../../common/store/inputs/constants'; + +import { isMlRule } from '../../../../../common/machine_learning/helpers'; +import { SecuritySolutionTabNavigation } from '../../../../common/components/navigation'; +import { InputsModelId } from '../../../../common/store/inputs/constants'; import { useDeepEqualSelector, useShallowEqualSelector, -} from '../../../../../common/hooks/use_selector'; -import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; -import { TableId } from '../../../../../../common/types/timeline'; -import type { UpdateDateRange } from '../../../../../common/components/charts/common'; -import { FiltersGlobal } from '../../../../../common/components/filters_global'; -import { FormattedDate } from '../../../../../common/components/formatted_date'; +} from '../../../../common/hooks/use_selector'; +import { useKibana, useUiSetting$ } from '../../../../common/lib/kibana'; +import { TableId } from '../../../../../common/types/timeline'; +import type { UpdateDateRange } from '../../../../common/components/charts/common'; +import { FiltersGlobal } from '../../../../common/components/filters_global'; +import { FormattedDate } from '../../../../common/components/formatted_date'; +import { tableDefaults } from '../../../../common/store/data_table/defaults'; +import { dataTableActions, dataTableSelectors } from '../../../../common/store/data_table'; import { - getEditRuleUrl, getRulesUrl, getDetectionEngineUrl, getRuleDetailsTabUrl, -} from '../../../../../common/components/link_to/redirect_to_detection_engine'; -import { SiemSearchBar } from '../../../../../common/components/search_bar'; -import { SecuritySolutionPageWrapper } from '../../../../../common/components/page_wrapper'; -import type { Rule } from '../../../../containers/detection_engine/rules'; -import { useListsConfig } from '../../../../containers/detection_engine/lists/use_lists_config'; -import { SpyRoute } from '../../../../../common/utils/route/spy_routes'; -import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_about_rule_details'; -import { AlertsHistogramPanel } from '../../../../components/alerts_kpis/alerts_histogram_panel'; -import { AlertsTable } from '../../../../components/alerts_table'; -import { useUserData } from '../../../../components/user_info'; -import { StepDefineRule } from '../../../../components/rules/step_define_rule'; -import { StepScheduleRule } from '../../../../components/rules/step_schedule_rule'; +} from '../../../../common/components/link_to/redirect_to_detection_engine'; +import { SiemSearchBar } from '../../../../common/components/search_bar'; +import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper'; +import type { Rule } from '../../../rule_management/logic'; +import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; +import { SpyRoute } from '../../../../common/utils/route/spy_routes'; +import { StepAboutRuleToggleDetails } from '../../../../detections/components/rules/step_about_rule_details'; +import { AlertsHistogramPanel } from '../../../../detections/components/alerts_kpis/alerts_histogram_panel'; +import { AlertsTable } from '../../../../detections/components/alerts_table'; +import { useUserData } from '../../../../detections/components/user_info'; +import { StepDefineRule } from '../../../../detections/components/rules/step_define_rule'; +import { StepScheduleRule } from '../../../../detections/components/rules/step_schedule_rule'; import { buildAlertsFilter, buildAlertStatusFilter, buildShowBuildingBlockFilter, buildThreatMatchFilter, -} from '../../../../components/alerts_table/default_config'; -import { RuleSwitch } from '../../../../components/rules/rule_switch'; -import { StepPanel } from '../../../../components/rules/step_panel'; -import { getStepsData, redirectToDetections, userHasPermissions } from '../helpers'; -import { useGlobalTime } from '../../../../../common/containers/use_global_time'; -import { inputsSelectors } from '../../../../../common/store/inputs'; -import { setAbsoluteRangeDatePicker } from '../../../../../common/store/inputs/actions'; -import { RuleActionsOverflow } from '../../../../components/rules/rule_actions_overflow'; -import { useMlCapabilities } from '../../../../../common/components/ml/hooks/use_ml_capabilities'; -import { hasMlAdminPermissions } from '../../../../../../common/machine_learning/has_ml_admin_permissions'; -import { hasMlLicense } from '../../../../../../common/machine_learning/has_ml_license'; -import { SecurityPageName } from '../../../../../app/types'; -import { LinkButton } from '../../../../../common/components/links'; -import { useFormatUrl } from '../../../../../common/components/link_to'; +} from '../../../../detections/components/alerts_table/default_config'; +import { RuleSwitch } from '../../../../detections/components/rules/rule_switch'; +import { StepPanel } from '../../../../detections/components/rules/step_panel'; +import { + getStepsData, + redirectToDetections, +} from '../../../../detections/pages/detection_engine/rules/helpers'; +import { useGlobalTime } from '../../../../common/containers/use_global_time'; +import { inputsSelectors } from '../../../../common/store/inputs'; +import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions'; +import { RuleActionsOverflow } from '../../../../detections/components/rules/rule_actions_overflow'; +import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; +import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; +import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license'; +import { SecurityPageName } from '../../../../app/types'; import { APP_UI_ID, DEFAULT_INDEX_KEY, DEFAULT_THREAT_INDEX_KEY, -} from '../../../../../../common/constants'; -import { useGlobalFullScreen } from '../../../../../common/containers/use_full_screen'; -import { Display } from '../../../../../hosts/pages/display'; +} from '../../../../../common/constants'; +import { useGlobalFullScreen } from '../../../../common/containers/use_full_screen'; +import { Display } from '../../../../hosts/pages/display'; import { focusUtilityBarAction, onTimelineTabKeyPressed, resetKeyboardFocus, showGlobalFilters, -} from '../../../../../timelines/components/timeline/helpers'; -import { useSourcererDataView } from '../../../../../common/containers/sourcerer'; -import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; +} from '../../../../timelines/components/timeline/helpers'; +import { useSourcererDataView } from '../../../../common/containers/sourcerer'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { - getToolTipContent, + explainLackOfPermission, canEditRuleWithActions, isBoolean, -} from '../../../../../common/utils/privileges'; + hasUserCRUDPermission, +} from '../../../../common/utils/privileges'; import { RuleStatus, RuleStatusFailedCallOut, ruleStatusI18n, -} from '../../../../components/rules/rule_execution_status'; -import { - ExecutionEventsTable, - useRuleExecutionSettings, -} from '../../../../../detection_engine/rule_monitoring'; +} from '../../../../detections/components/rules/rule_execution_status'; +import { ExecutionEventsTable, useRuleExecutionSettings } from '../../../rule_monitoring'; import { ExecutionLogTable } from './execution_log_table/execution_log_table'; -import * as detectionI18n from '../../translations'; -import * as ruleI18n from '../translations'; +import * as detectionI18n from '../../../../detections/pages/detection_engine/translations'; +import * as ruleI18n from '../../../../detections/pages/detection_engine/rules/translations'; import { RuleDetailsContextProvider } from './rule_details_context'; -import { useGetSavedQuery } from '../use_get_saved_query'; +import { useGetSavedQuery } from '../../../../detections/pages/detection_engine/rules/use_get_saved_query'; import * as i18n from './translations'; -import { NeedAdminForUpdateRulesCallOut } from '../../../../components/callouts/need_admin_for_update_callout'; -import { MissingPrivilegesCallOut } from '../../../../components/callouts/missing_privileges_callout'; -import { useRuleWithFallback } from '../../../../containers/detection_engine/rules/use_rule_with_fallback'; -import type { BadgeOptions } from '../../../../../common/components/header_page/types'; -import type { AlertsStackByField } from '../../../../components/alerts_kpis/common/types'; -import type { Status } from '../../../../../../common/detection_engine/schemas/common/schemas'; +import { NeedAdminForUpdateRulesCallOut } from '../../../../detections/components/callouts/need_admin_for_update_callout'; +import { MissingPrivilegesCallOut } from '../../../../detections/components/callouts/missing_privileges_callout'; +import { useRuleWithFallback } from '../../../rule_management/logic/use_rule_with_fallback'; +import type { BadgeOptions } from '../../../../common/components/header_page/types'; +import type { AlertsStackByField } from '../../../../detections/components/alerts_kpis/common/types'; +import type { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; import { AlertsTableFilterGroup, FILTER_OPEN, -} from '../../../../components/alerts_table/alerts_filter_group'; -import { useSignalHelpers } from '../../../../../common/containers/sourcerer/use_signal_helpers'; -import { HeaderPage } from '../../../../../common/components/header_page'; -import { ExceptionsViewer } from '../../../../../detection_engine/rule_exceptions/components/all_exception_items_table'; -import type { NavTab } from '../../../../../common/components/navigation/types'; +} from '../../../../detections/components/alerts_table/alerts_filter_group'; +import { useSignalHelpers } from '../../../../common/containers/sourcerer/use_signal_helpers'; +import { HeaderPage } from '../../../../common/components/header_page'; +import { ExceptionsViewer } from '../../../rule_exceptions/components/all_exception_items_table'; +import type { NavTab } from '../../../../common/components/navigation/types'; +import { EditRuleSettingButtonLink } from '../../../../detections/pages/detection_engine/rules/details/components/edit_rule_settings_button_link'; /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. @@ -299,7 +299,6 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({ const [showBuildingBlockAlerts, setShowBuildingBlockAlerts] = useState(false); const [showOnlyThreatIndicatorAlerts, setShowOnlyThreatIndicatorAlerts] = useState(false); const mlCapabilities = useMlCapabilities(); - const { formatUrl } = useFormatUrl(SecurityPageName.rules); const { globalFullScreen } = useGlobalFullScreen(); const [filterGroup, setFilterGroup] = useState<Status>(FILTER_OPEN); const [dataViewOptions, setDataViewOptions] = useState<{ [x: string]: DataViewListItem }>({}); @@ -584,45 +583,6 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({ setRule((currentRule) => (currentRule ? { ...currentRule, enabled } : currentRule)); }, []); - const goToEditRule = useCallback( - (ev) => { - ev.preventDefault(); - navigateToApp(APP_UI_ID, { - deepLinkId: SecurityPageName.rules, - path: getEditRuleUrl(ruleId ?? ''), - }); - }, - [navigateToApp, ruleId] - ); - - const editRule = useMemo(() => { - if (!hasActionsPrivileges) { - return ( - <EuiToolTip position="top" content={ruleI18n.EDIT_RULE_SETTINGS_TOOLTIP}> - <LinkButton - onClick={goToEditRule} - iconType="controlsHorizontal" - isDisabled={true} - href={formatUrl(getEditRuleUrl(ruleId ?? ''))} - > - {ruleI18n.EDIT_RULE_SETTINGS} - </LinkButton> - </EuiToolTip> - ); - } - return ( - <LinkButton - data-test-subj="editRuleSettingsLink" - onClick={goToEditRule} - iconType="controlsHorizontal" - isDisabled={!isExistingRule || !userHasPermissions(canUserCRUD)} - href={formatUrl(getEditRuleUrl(ruleId ?? ''))} - > - {ruleI18n.EDIT_RULE_SETTINGS} - </LinkButton> - ); - }, [isExistingRule, canUserCRUD, formatUrl, goToEditRule, hasActionsPrivileges, ruleId]); - const onShowBuildingBlockAlertsChangedCallback = useCallback( (newShowBuildingBlockAlerts: boolean) => { setShowBuildingBlockAlerts(newShowBuildingBlockAlerts); @@ -719,7 +679,12 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({ <EuiFlexItem grow={false}> <EuiToolTip position="top" - content={getToolTipContent(rule, hasMlPermissions, hasActionsPrivileges)} + content={explainLackOfPermission( + rule, + hasMlPermissions, + hasActionsPrivileges, + canUserCRUD + )} > <EuiFlexGroup> <RuleSwitch @@ -727,8 +692,8 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({ isDisabled={ !isExistingRule || !canEditRuleWithActions(rule, hasActionsPrivileges) || - !userHasPermissions(canUserCRUD) || - (!hasMlPermissions && !rule?.enabled) + !hasUserCRUDPermission(canUserCRUD) || + (isMlRule(rule?.type) && !hasMlPermissions) } enabled={isExistingRule && (rule?.enabled ?? false)} onChange={handleOnChangeEnabledRule} @@ -740,11 +705,26 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({ <EuiFlexItem grow={false}> <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}> - <EuiFlexItem grow={false}>{editRule}</EuiFlexItem> + <EuiFlexItem grow={false}> + <EditRuleSettingButtonLink + ruleId={ruleId} + disabled={ + !isExistingRule || + !hasUserCRUDPermission(canUserCRUD) || + (isMlRule(rule?.type) && !hasMlPermissions) + } + disabledReason={explainLackOfPermission( + rule, + hasMlPermissions, + hasActionsPrivileges, + canUserCRUD + )} + /> + </EuiFlexItem> <EuiFlexItem grow={false}> <RuleActionsOverflow rule={rule} - userHasPermissions={isExistingRule && userHasPermissions(canUserCRUD)} + userHasPermissions={isExistingRule && hasUserCRUDPermission(canUserCRUD)} canDuplicateRuleWithActions={canEditRuleWithActions( rule, hasActionsPrivileges diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/rule_details_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/rule_details_context.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/rule_details_context.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/rule_details_context.tsx index 58b34eb4a2bee..92de2e337dcad 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/rule_details_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/rule_details_context.tsx @@ -8,13 +8,13 @@ import type { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { DurationRange } from '@elastic/eui/src/components/date_picker/types'; import React, { createContext, useContext, useMemo, useState } from 'react'; -import { RULE_DETAILS_EXECUTION_LOG_TABLE_SHOW_METRIC_COLUMNS_STORAGE_KEY } from '../../../../../../common/constants'; +import { RULE_DETAILS_EXECUTION_LOG_TABLE_SHOW_METRIC_COLUMNS_STORAGE_KEY } from '../../../../../common/constants'; import type { RuleExecutionResult, RuleExecutionStatus, -} from '../../../../../../common/detection_engine/rule_monitoring'; -import { invariant } from '../../../../../../common/utils/invariant'; -import { useKibana } from '../../../../../common/lib/kibana'; +} from '../../../../../common/detection_engine/rule_monitoring'; +import { invariant } from '../../../../../common/utils/invariant'; +import { useKibana } from '../../../../common/lib/kibana'; import { RuleDetailTabs } from '.'; export interface ExecutionLogTableState { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/translations.ts diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx index 0613f08b7f572..d422c7c5ce6bf 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx @@ -23,7 +23,7 @@ import { useCreateOrUpdateException } from '../../logic/use_create_update_except import { useFetchIndexPatterns } from '../../logic/use_exception_flyout_data'; import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; import * as helpers from '../../utils/helpers'; -import type { Rule } from '../../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../../rule_management/logic/types'; import * as i18n from './translations'; import { TestProviders } from '../../../../common/mock'; @@ -31,15 +31,14 @@ import { TestProviders } from '../../../../common/mock'; import { getRulesEqlSchemaMock, getRulesSchemaMock, -} from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; +} from '../../../../../common/detection_engine/rule_schema/mocks'; import type { AlertData } from '../../utils/types'; -import { useFindRules } from '../../../../detections/pages/detection_engine/rules/all/rules_table/use_find_rules'; +import { useFindRules } from '../../../rule_management/logic/use_find_rules'; import { useFindExceptionListReferences } from '../../logic/use_find_references'; jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index'); jest.mock('../../../../common/lib/kibana'); jest.mock('../../../../common/containers/source'); -jest.mock('../../../../detections/containers/detection_engine/rules'); jest.mock('../../logic/use_create_update_exception'); jest.mock('../../logic/use_exception_flyout_data'); jest.mock('../../logic/use_find_references'); @@ -47,9 +46,9 @@ jest.mock('@kbn/securitysolution-hook-utils', () => ({ ...jest.requireActual('@kbn/securitysolution-hook-utils'), useAsync: jest.fn(), })); -jest.mock('../../../../detections/containers/detection_engine/rules/use_rule_async'); +jest.mock('../../../rule_management/logic/use_rule'); jest.mock('@kbn/lists-plugin/public'); -jest.mock('../../../../detections/pages/detection_engine/rules/all/rules_table/use_find_rules'); +jest.mock('../../../rule_management/logic/use_find_rules'); const mockGetExceptionBuilderComponentLazy = getExceptionBuilderComponentLazy as jest.Mock< ReturnType<typeof getExceptionBuilderComponentLazy> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx index 6d190913e358d..413973faff766 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx @@ -24,16 +24,18 @@ import { EuiCallOut, EuiText, } from '@elastic/eui'; + +import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import type { OsTypeArray, ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; import type { ExceptionsBuilderExceptionItem, ExceptionsBuilderReturnExceptionItem, } from '@kbn/securitysolution-list-utils'; -import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; import type { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; import * as i18n from './translations'; +import { ExceptionItemComments } from '../item_comments'; import { defaultEndpointExceptionItems, retrieveAlertOsTypes, @@ -44,14 +46,13 @@ import { initialState, createExceptionItemsReducer } from './reducer'; import { ExceptionsFlyoutMeta } from '../flyout_components/item_meta_form'; import { ExceptionsConditions } from '../flyout_components/item_conditions'; import { useFetchIndexPatterns } from '../../logic/use_exception_flyout_data'; -import type { Rule } from '../../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../../rule_management/logic/types'; import { ExceptionItemsFlyoutAlertsActions } from '../flyout_components/alerts_actions'; import { ExceptionsAddToRulesOrLists } from '../flyout_components/add_exception_to_rule_or_list'; import { useAddNewExceptionItems } from './use_add_new_exceptions'; import { entrichNewExceptionItems } from '../flyout_components/utils'; import { useCloseAlertsFromExceptions } from '../../logic/use_close_alerts'; import { ruleTypesThatAllowLargeValueLists } from '../../utils/constants'; -import { ExceptionItemComments } from '../item_comments'; const SectionHeader = styled(EuiTitle)` ${() => css` diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts index 3e5f8afd19626..c36c842930b0f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts @@ -12,7 +12,7 @@ import type { ExceptionsBuilderReturnExceptionItem, } from '@kbn/securitysolution-list-utils'; -import type { Rule } from '../../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../../rule_management/logic/types'; export interface State { exceptionItemMeta: { name: string }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/use_add_new_exceptions.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/use_add_new_exceptions.ts index 909d8e8580472..7aa686ed43cdf 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/use_add_new_exceptions.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/use_add_new_exceptions.ts @@ -22,7 +22,7 @@ import type { ExceptionsBuilderReturnExceptionItem } from '@kbn/securitysolution import * as i18n from './translations'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { Rule } from '../../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../../rule_management/logic/types'; import { useCreateOrUpdateException } from '../../logic/use_create_update_exception'; import { useAddRuleDefaultException } from '../../logic/use_add_rule_exception'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx index 6f42a4627135a..dc6a0ea238310 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx @@ -13,8 +13,8 @@ import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { ExceptionsViewer } from '.'; import { useKibana } from '../../../../common/lib/kibana'; import { TestProviders } from '../../../../common/mock'; -import type { Rule } from '../../../../detections/containers/detection_engine/rules/types'; -import { mockRule } from '../../../../detections/pages/detection_engine/rules/all/__mocks__/mock'; +import type { Rule } from '../../../rule_management/logic/types'; +import { mockRule } from '../../../rule_management_ui/components/rules_table/__mocks__/mock'; import { useFindExceptionListReferences } from '../../logic/use_find_references'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx index 6de23a76981b8..f0c71819d4bce 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx @@ -38,7 +38,7 @@ import { EditExceptionFlyout } from '../edit_exception_flyout'; import { AddExceptionFlyout } from '../add_exception_flyout'; import * as i18n from './translations'; import { useFindExceptionListReferences } from '../../logic/use_find_references'; -import type { Rule } from '../../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../../rule_management/logic/types'; const StyledText = styled(EuiText)` font-style: italic; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx index c313d8cc2aeeb..09ed03130ef08 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx @@ -19,13 +19,14 @@ import { useFetchIndex } from '../../../../common/containers/source'; import { createStubIndexPattern, stubIndexPattern } from '@kbn/data-plugin/common/stubs'; import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; -import type { Rule } from '../../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../../rule_management/logic/types'; import type { EntriesArray } from '@kbn/securitysolution-io-ts-list-types'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { getRulesEqlSchemaMock, getRulesSchemaMock, -} from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; +} from '../../../../../common/detection_engine/rule_schema/mocks'; + import { getMockTheme } from '../../../../common/lib/kibana/kibana_react.mock'; import { getExceptionBuilderComponentLazy } from '@kbn/lists-plugin/public'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; @@ -44,14 +45,13 @@ const mockTheme = getMockTheme({ }); jest.mock('../../../../common/lib/kibana'); -jest.mock('../../../../detections/containers/detection_engine/rules'); jest.mock('../../logic/use_create_update_exception'); jest.mock('../../logic/use_exception_flyout_data'); jest.mock('../../../../common/containers/source'); jest.mock('../../logic/use_find_references'); jest.mock('../../logic/use_fetch_or_create_rule_exception_list'); jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index'); -jest.mock('../../../../detections/containers/detection_engine/rules/use_rule_async'); +jest.mock('../../../rule_management/logic/use_rule'); jest.mock('@kbn/lists-plugin/public'); const mockGetExceptionBuilderComponentLazy = getExceptionBuilderComponentLazy as jest.Mock< diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx index d6dbc402a92e6..6b85b4bf7302f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useReducer } from 'react'; import styled, { css } from 'styled-components'; import { @@ -30,28 +31,31 @@ import { exceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; -import { isEmpty } from 'lodash/fp'; import type { ExceptionsBuilderReturnExceptionItem } from '@kbn/securitysolution-list-utils'; -import * as i18n from './translations'; -import { ExceptionsFlyoutMeta } from '../flyout_components/item_meta_form'; -import { createExceptionItemsReducer } from './reducer'; -import { ExceptionsLinkedToLists } from '../flyout_components/linked_to_list'; -import { ExceptionsLinkedToRule } from '../flyout_components/linked_to_rule'; -import type { Rule } from '../../../../detections/containers/detection_engine/rules/types'; -import { ExceptionItemsFlyoutAlertsActions } from '../flyout_components/alerts_actions'; -import { ExceptionsConditions } from '../flyout_components/item_conditions'; + import { isEqlRule, isNewTermsRule, isThresholdRule, } from '../../../../../common/detection_engine/utils'; -import { useFetchIndexPatterns } from '../../logic/use_exception_flyout_data'; + +import type { Rule } from '../../../rule_management/logic/types'; +import { ExceptionsFlyoutMeta } from '../flyout_components/item_meta_form'; +import { ExceptionsLinkedToLists } from '../flyout_components/linked_to_list'; +import { ExceptionsLinkedToRule } from '../flyout_components/linked_to_rule'; +import { ExceptionItemsFlyoutAlertsActions } from '../flyout_components/alerts_actions'; +import { ExceptionsConditions } from '../flyout_components/item_conditions'; + import { filterIndexPatterns } from '../../utils/helpers'; -import { entrichExceptionItemsForUpdate } from '../flyout_components/utils'; -import { useEditExceptionItems } from './use_edit_exception'; +import { useFetchIndexPatterns } from '../../logic/use_exception_flyout_data'; import { useCloseAlertsFromExceptions } from '../../logic/use_close_alerts'; import { useFindExceptionListReferences } from '../../logic/use_find_references'; +import { entrichExceptionItemsForUpdate } from '../flyout_components/utils'; import { ExceptionItemComments } from '../item_comments'; +import { createExceptionItemsReducer } from './reducer'; +import { useEditExceptionItems } from './use_edit_exception'; + +import * as i18n from './translations'; interface EditExceptionFlyoutProps { list: ExceptionListSchema; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/error_callout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/error_callout/index.test.tsx index d67e62dcb52dc..782066527d13a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/error_callout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/error_callout/index.test.tsx @@ -10,21 +10,19 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from '@kbn/core/public/mocks'; import { getListMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; -import { useDissasociateExceptionList } from '../../../../detections/containers/detection_engine/rules/use_dissasociate_exception_list'; +import { useDisassociateExceptionList } from '../../../rule_management/logic/use_disassociate_exception_list'; import { ErrorCallout } from '.'; -import { savedRuleMock } from '../../../../detections/containers/detection_engine/rules/mock'; +import { savedRuleMock } from '../../../rule_management/logic/mock'; -jest.mock( - '../../../../detections/containers/detection_engine/rules/use_dissasociate_exception_list' -); +jest.mock('../../../rule_management/logic/use_disassociate_exception_list'); const mockKibanaHttpService = coreMock.createStart().http; describe('ErrorCallout', () => { - const mockDissasociate = jest.fn(); + const mockDisassociate = jest.fn(); beforeEach(() => { - (useDissasociateExceptionList as jest.Mock).mockReturnValue([false, mockDissasociate]); + (useDisassociateExceptionList as jest.Mock).mockReturnValue([false, mockDisassociate]); }); it('it renders error details', () => { @@ -104,7 +102,7 @@ describe('ErrorCallout', () => { expect(wrapper.find('[data-test-subj="errorCalloutMessage"]').at(0).text()).toEqual( 'Error fetching exception list' ); - expect(wrapper.find('[data-test-subj="errorCalloutDissasociateButton"]').exists()).toBeFalsy(); + expect(wrapper.find('[data-test-subj="errorCalloutDisassociateButton"]').exists()).toBeFalsy(); }); it('it renders specific missing exceptions list error', () => { @@ -133,10 +131,10 @@ describe('ErrorCallout', () => { expect(wrapper.find('[data-test-subj="errorCalloutMessage"]').at(0).text()).toEqual( 'The associated exception list (some_uuid) no longer exists. Please remove the missing exception list to add additional exceptions to the detection rule.' ); - expect(wrapper.find('[data-test-subj="errorCalloutDissasociateButton"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="errorCalloutDisassociateButton"]').exists()).toBeTruthy(); }); - it('it dissasociates list from rule when remove exception list clicked ', () => { + it('it disassociates list from rule when remove exception list clicked ', () => { const wrapper = mountWithIntl( <ErrorCallout http={mockKibanaHttpService} @@ -153,8 +151,8 @@ describe('ErrorCallout', () => { /> ); - wrapper.find('[data-test-subj="errorCalloutDissasociateButton"]').at(0).simulate('click'); + wrapper.find('[data-test-subj="errorCalloutDisassociateButton"]').at(0).simulate('click'); - expect(mockDissasociate).toHaveBeenCalledWith([]); + expect(mockDisassociate).toHaveBeenCalledWith([]); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/error_callout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/error_callout/index.tsx index 84583ad5f3139..104481b6e111a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/error_callout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/error_callout/index.tsx @@ -18,9 +18,9 @@ import { import type { List } from '@kbn/securitysolution-io-ts-list-types'; import type { HttpSetup } from '@kbn/core/public'; -import type { Rule } from '../../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../../rule_management/logic/types'; import * as i18n from '../../utils/translations'; -import { useDissasociateExceptionList } from '../../../../detections/containers/detection_engine/rules/use_dissasociate_exception_list'; +import { useDisassociateExceptionList } from '../../../rule_management/logic/use_disassociate_exception_list'; export interface ErrorInfo { reason: string | null; @@ -54,7 +54,7 @@ const ErrorCalloutComponent = ({ onSuccess(listToDelete != null ? listToDelete.id : ''); }, [onSuccess, listToDelete]); - const [isDissasociatingList, handleDissasociateExceptionList] = useDissasociateExceptionList({ + const [isDisassociatingList, handleDisassociateExceptionList] = useDisassociateExceptionList({ http, ruleRuleId: rule != null ? rule.rule_id : '', onSuccess: handleOnSuccess, @@ -66,8 +66,8 @@ const ErrorCalloutComponent = ({ errorInfo.code === 404 && rule != null && listToDelete != null && - handleDissasociateExceptionList != null, - [errorInfo.code, listToDelete, handleDissasociateExceptionList, rule] + handleDisassociateExceptionList != null, + [errorInfo.code, listToDelete, handleDisassociateExceptionList, rule] ); useEffect((): void => { @@ -80,23 +80,23 @@ const ErrorCalloutComponent = ({ setErrorTitle(`${errorInfo.reason}${errorInfo.code != null ? ` (${errorInfo.code})` : ''}`); }, [errorInfo.reason, errorInfo.code, listToDelete, canDisplay404Actions]); - const handleDissasociateList = useCallback((): void => { + const handleDisassociateList = useCallback((): void => { // Yes, it's redundant, unfortunately typescript wasn't picking up - // that `handleDissasociateExceptionList` and `list` are checked in + // that `handleDisassociateExceptionList` and `list` are checked in // canDisplay404Actions if ( canDisplay404Actions && rule != null && listToDelete != null && - handleDissasociateExceptionList != null + handleDisassociateExceptionList != null ) { const exceptionLists = (rule.exceptions_list ?? []).filter( ({ id }) => id !== listToDelete.id ); - handleDissasociateExceptionList(exceptionLists); + handleDisassociateExceptionList(exceptionLists); } - }, [handleDissasociateExceptionList, listToDelete, canDisplay404Actions, rule]); + }, [handleDisassociateExceptionList, listToDelete, canDisplay404Actions, rule]); useEffect((): void => { if (errorInfo.code === 404 && rule != null && rule.exceptions_list != null) { @@ -144,16 +144,16 @@ const ErrorCalloutComponent = ({ <EuiButtonEmpty data-test-subj="errorCalloutCancelButton" color="danger" - isDisabled={isDissasociatingList} + isDisabled={isDisassociatingList} onClick={onCancel} > {i18n.CANCEL} </EuiButtonEmpty> {canDisplay404Actions && ( <EuiButton - data-test-subj="errorCalloutDissasociateButton" - isLoading={isDissasociatingList} - onClick={handleDissasociateList} + data-test-subj="errorCalloutDisassociateButton" + isLoading={isDisassociatingList} + onClick={handleDisassociateList} color="danger" > {i18n.CLEAR_EXCEPTIONS_LABEL} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/index.tsx index 233e11b0709eb..b841bac12f9d9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/index.tsx @@ -16,7 +16,7 @@ import * as i18n from './translations'; import { ExceptionItemCardHeader } from './header'; import { ExceptionItemCardConditions } from './conditions'; import { ExceptionItemCardMetaInfo } from './meta'; -import type { ExceptionListRuleReferencesSchema } from '../../../../../common/detection_engine/schemas/response'; +import type { ExceptionListRuleReferencesSchema } from '../../../../../common/detection_engine/rule_exceptions'; import { ExceptionItemCardComments } from './comments'; export interface ExceptionItemProps { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/meta.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/meta.tsx index 8005264636bfa..c025a2dc2a2cb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/meta.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/exception_item_card/meta.tsx @@ -25,9 +25,9 @@ import styled from 'styled-components'; import * as i18n from './translations'; import { FormattedDate } from '../../../../common/components/formatted_date'; import { SecurityPageName } from '../../../../../common/constants'; -import type { ExceptionListRuleReferencesSchema } from '../../../../../common/detection_engine/schemas/response'; +import type { ExceptionListRuleReferencesSchema } from '../../../../../common/detection_engine/rule_exceptions'; import { SecuritySolutionLinkAnchor } from '../../../../common/components/links'; -import { RuleDetailTabs } from '../../../../detections/pages/detection_engine/rules/details'; +import { RuleDetailTabs } from '../../../rule_details_ui/pages/rule_details'; import { getRuleDetailsTabUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; const StyledFlexItem = styled(EuiFlexItem)` diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_exception_to_rule_or_list/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_exception_to_rule_or_list/index.test.tsx index d9038b0c3fd04..6f81b8177b5f6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_exception_to_rule_or_list/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_exception_to_rule_or_list/index.test.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { getRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; -import type { Rule } from '../../../../../detections/containers/detection_engine/rules/types'; +import { getRulesSchemaMock } from '../../../../../../common/detection_engine/rule_schema/mocks'; +import type { Rule } from '../../../../rule_management/logic/types'; import { ExceptionsAddToRulesOrLists } from '.'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_exception_to_rule_or_list/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_exception_to_rule_or_list/index.tsx index aa705891ecfe7..67e66cc8faa13 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_exception_to_rule_or_list/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_exception_to_rule_or_list/index.tsx @@ -12,7 +12,7 @@ import type { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import * as i18n from './translations'; -import type { Rule } from '../../../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../../../rule_management/logic/types'; import { ExceptionsAddToRulesOptions } from '../add_to_rules_options'; import { ExceptionsAddToListsOptions } from '../add_to_lists_options'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_lists_options/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_lists_options/index.test.tsx index 4b55522a638e2..b8911e47d1eb6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_lists_options/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_lists_options/index.test.tsx @@ -11,7 +11,7 @@ import { shallow } from 'enzyme'; import { ExceptionsAddToListsOptions } from '.'; -jest.mock('../../../../../detections/pages/detection_engine/rules/all/rules_table/use_find_rules'); +jest.mock('../../../../rule_management/logic/use_find_rules'); describe('ExceptionsAddToListsOptions', () => { it('it displays radio option as disabled if there are no "sharedLists"', () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_lists_table/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_lists_table/index.tsx index f8dc8e41a6df4..d99480ae565cb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_lists_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_lists_table/index.tsx @@ -11,11 +11,11 @@ import { EuiText, EuiSpacer, EuiInMemoryTable, EuiPanel, EuiLoadingContent } fro import type { ExceptionListSchema, ListArray } from '@kbn/securitysolution-io-ts-list-types'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; -import type { FindRulesReferencedByExceptionsListProp } from '../../../../../detections/containers/detection_engine/rules'; +import type { FindRulesReferencedByExceptionsListProp } from '../../../../rule_management/logic'; import * as i18n from './translations'; import { getSharedListsTableColumns } from '../utils'; import { useFindExceptionListReferences } from '../../../logic/use_find_references'; -import type { ExceptionListRuleReferencesSchema } from '../../../../../../common/detection_engine/schemas/response'; +import type { ExceptionListRuleReferencesSchema } from '../../../../../../common/detection_engine/rule_exceptions'; interface ExceptionsAddToListsComponentProps { /** diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.test.tsx index 8084231a53676..fe65b3ce0384e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.test.tsx @@ -10,15 +10,17 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ExceptionsAddToRulesOptions } from '.'; import { TestProviders } from '../../../../../common/mock'; -import { useFindRules } from '../../../../../detections/pages/detection_engine/rules/all/rules_table/use_find_rules'; -import { getRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; -import type { Rule } from '../../../../../detections/containers/detection_engine/rules/types'; +import { useFindRulesInMemory } from '../../../../rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory'; +import { getRulesSchemaMock } from '../../../../../../common/detection_engine/rule_schema/mocks'; +import type { Rule } from '../../../../rule_management/logic/types'; -jest.mock('../../../../../detections/pages/detection_engine/rules/all/rules_table/use_find_rules'); +jest.mock( + '../../../../rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory' +); describe('ExceptionsAddToRulesOptions', () => { beforeEach(() => { - (useFindRules as jest.Mock).mockReturnValue({ + (useFindRulesInMemory as jest.Mock).mockReturnValue({ data: { rules: [getRulesSchemaMock(), { ...getRulesSchemaMock(), id: '345', name: 'My rule' }], total: 0, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.tsx index 424f8221b5a48..67ef993479713 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_options/index.tsx @@ -9,7 +9,7 @@ import React, { useMemo } from 'react'; import { EuiRadio, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { Rule } from '../../../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../../../rule_management/logic/types'; import { ExceptionsAddToRulesTable } from '../add_to_rules_table'; export type AddToRuleListsRadioOptions = 'select_rules_to_add_to' | 'add_to_rules' | 'add_to_rule'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.test.tsx index d0d5a265a2c4f..48a710cc66ad8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.test.tsx @@ -10,15 +10,17 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ExceptionsAddToRulesTable } from '.'; import { TestProviders } from '../../../../../common/mock'; -import { useFindRules } from '../../../../../detections/pages/detection_engine/rules/all/rules_table/use_find_rules'; -import { getRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; -import type { Rule } from '../../../../../detections/containers/detection_engine/rules/types'; +import { useFindRulesInMemory } from '../../../../rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory'; +import { getRulesSchemaMock } from '../../../../../../common/detection_engine/rule_schema/mocks'; +import type { Rule } from '../../../../rule_management/logic/types'; -jest.mock('../../../../../detections/pages/detection_engine/rules/all/rules_table/use_find_rules'); +jest.mock( + '../../../../rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory' +); describe('ExceptionsAddToRulesTable', () => { it('it displays loading state while fetching rules', () => { - (useFindRules as jest.Mock).mockReturnValue({ + (useFindRulesInMemory as jest.Mock).mockReturnValue({ data: { rules: [], total: 0 }, isFetched: false, }); @@ -34,7 +36,7 @@ describe('ExceptionsAddToRulesTable', () => { }); it('it displays fetched rules', () => { - (useFindRules as jest.Mock).mockReturnValue({ + (useFindRulesInMemory as jest.Mock).mockReturnValue({ data: { rules: [getRulesSchemaMock(), { ...getRulesSchemaMock(), id: '345', name: 'My rule' }], total: 0, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.tsx index 6b56b0eceb2f3..b4112228c923f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/index.tsx @@ -11,8 +11,8 @@ import { EuiSpacer, EuiPanel, EuiText, EuiInMemoryTable, EuiLoadingContent } fro import { i18n } from '@kbn/i18n'; import * as myI18n from './translations'; -import type { Rule } from '../../../../../detections/containers/detection_engine/rules/types'; -import { useFindRules } from '../../../../../detections/pages/detection_engine/rules/all/rules_table/use_find_rules'; +import type { Rule } from '../../../../rule_management/logic/types'; +import { useFindRulesInMemory } from '../../../../rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory'; import { getRulesTableColumn } from '../utils'; interface ExceptionsAddToRulesComponentProps { @@ -24,7 +24,7 @@ const ExceptionsAddToRulesTableComponent: React.FC<ExceptionsAddToRulesComponent initiallySelectedRules, onRuleSelectionChange, }) => { - const { data: { rules } = { rules: [], total: 0 }, isFetched } = useFindRules({ + const { data: { rules } = { rules: [], total: 0 }, isFetched } = useFindRulesInMemory({ isInMemorySorting: true, filterOptions: { filter: '', diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.test.tsx index a5d6de83b0655..81f8925565f36 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.test.tsx @@ -10,8 +10,8 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ExceptionsConditions } from '.'; import { TestProviders, mockIndexPattern } from '../../../../../common/mock'; -import { getRulesEqlSchemaMock } from '../../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; -import type { Rule } from '../../../../../detections/containers/detection_engine/rules/types'; +import { getRulesEqlSchemaMock } from '../../../../../../common/detection_engine/rule_schema/mocks'; +import type { Rule } from '../../../../rule_management/logic/types'; import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.tsx index 4869c80c172a2..c1f11b17cbbd9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/item_conditions/index.tsx @@ -27,7 +27,7 @@ import type { DataViewBase } from '@kbn/es-query'; import styled, { css } from 'styled-components'; import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; import { hasEqlSequenceQuery, isEqlRule } from '../../../../../../common/detection_engine/utils'; -import type { Rule } from '../../../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../../../rule_management/logic/types'; import { useKibana } from '../../../../../common/lib/kibana'; import * as i18n from './translations'; import * as sharedI18n from '../../../utils/translations'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_list/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_list/index.tsx index 55a748ae7dffd..f09b15bda16a1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_list/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_list/index.tsx @@ -10,7 +10,7 @@ import { EuiTitle, EuiSpacer, EuiPanel, EuiInMemoryTable, EuiLoadingContent } fr import styled, { css } from 'styled-components'; import * as i18n from './translations'; -import type { ExceptionListRuleReferencesSchema } from '../../../../../../common/detection_engine/schemas/response'; +import type { ExceptionListRuleReferencesSchema } from '../../../../../../common/detection_engine/rule_exceptions'; import { getSharedListsTableColumns } from '../utils'; interface ExceptionsLinkedToListComponentProps { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.test.tsx index 56cf481282d34..42e59832f8abb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.test.tsx @@ -10,10 +10,10 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ExceptionsLinkedToRule } from '.'; import { TestProviders } from '../../../../../common/mock'; -import { getRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; -import type { Rule } from '../../../../../detections/containers/detection_engine/rules/types'; +import { getRulesSchemaMock } from '../../../../../../common/detection_engine/rule_schema/mocks'; +import type { Rule } from '../../../../rule_management/logic/types'; -jest.mock('../../../../../detections/pages/detection_engine/rules/all/rules_table/use_find_rules'); +jest.mock('../../../../rule_management/logic/use_find_rules'); describe('ExceptionsLinkedToRule', () => { it('it displays rule name and link', () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.tsx index 289660317025c..64a378e8fb33a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/linked_to_rule/index.tsx @@ -10,7 +10,7 @@ import { EuiTitle, EuiSpacer, EuiInMemoryTable } from '@elastic/eui'; import styled, { css } from 'styled-components'; import * as i18n from './translations'; -import type { Rule } from '../../../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../../../rule_management/logic/types'; import { getRulesTableColumn } from '../utils'; interface ExceptionsLinkedToRuleComponentProps { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx index 77e881fdad6f0..a8674883253c0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/utils.tsx @@ -22,14 +22,14 @@ import { } from '../../utils/helpers'; import { SecuritySolutionLinkAnchor } from '../../../../common/components/links'; import { getRuleDetailsTabUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; -import { RuleDetailTabs } from '../../../../detections/pages/detection_engine/rules/details'; +import { RuleDetailTabs } from '../../../rule_details_ui/pages/rule_details'; import { SecurityPageName } from '../../../../../common/constants'; import { PopoverItems } from '../../../../common/components/popover_items'; import type { ExceptionListRuleReferencesInfoSchema, ExceptionListRuleReferencesSchema, -} from '../../../../../common/detection_engine/schemas/response'; -import type { Rule } from '../../../../detections/containers/detection_engine/rules/types'; +} from '../../../../../common/detection_engine/rule_exceptions'; +import type { Rule } from '../../../rule_management/logic/types'; import * as i18n from './translations'; /** diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_add_rule_exception.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_add_rule_exception.tsx index 7cf4ff2d8417d..c25ffda8ece14 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_add_rule_exception.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_add_rule_exception.tsx @@ -11,8 +11,8 @@ import type { } from '@kbn/securitysolution-io-ts-list-types'; import { useEffect, useRef, useState } from 'react'; -import { addRuleExceptions } from '../../../detections/containers/detection_engine/rules/api'; -import type { Rule } from '../../../detections/containers/detection_engine/rules/types'; +import { addRuleExceptions } from '../../rule_management/api/api'; +import type { Rule } from '../../rule_management/logic/types'; /** * Adds exception items to rules default exception list diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_close_alerts.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_close_alerts.tsx index 5dc960cb96e2c..a6dce444527d0 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_close_alerts.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_close_alerts.tsx @@ -16,7 +16,7 @@ import { buildAlertsFilter, } from '../../../detections/components/alerts_table/default_config'; import { getEsQueryFilter } from '../../../detections/containers/detection_engine/exceptions/get_es_query_filter'; -import type { Index } from '../../../../common/detection_engine/schemas/common/schemas'; +import type { IndexPatternArray } from '../../../../common/detection_engine/rule_schema'; import { prepareExceptionItemsForBulkClose } from '../utils/helpers'; import * as i18nCommon from '../../../common/translations'; import * as i18n from './translations'; @@ -35,7 +35,7 @@ export type AddOrUpdateExceptionItemsFunc = ( ruleStaticIds: string[], exceptionItems: ExceptionListItemSchema[], alertIdToClose?: string, - bulkCloseIndex?: Index + bulkCloseIndex?: IndexPatternArray ) => Promise<void>; export type ReturnUseCloseAlertsFromExceptions = [boolean, AddOrUpdateExceptionItemsFunc | null]; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_exception_flyout_data.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_exception_flyout_data.tsx index 57cfda994d37c..cec9be976592e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_exception_flyout_data.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_exception_flyout_data.tsx @@ -8,7 +8,7 @@ import { useEffect, useState, useMemo } from 'react'; import type { DataViewBase } from '@kbn/es-query'; -import type { Rule } from '../../../detections/containers/detection_engine/rules/types'; +import type { Rule } from '../../rule_management/logic/types'; import { useGetInstalledJob } from '../../../common/components/ml/hooks/use_get_jobs'; import { useKibana } from '../../../common/lib/kibana'; import { useFetchIndex } from '../../../common/containers/source'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.test.tsx index d3bc6214b28ac..c4085910faf25 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.test.tsx @@ -9,10 +9,10 @@ import type { RenderHookResult } from '@testing-library/react-hooks'; import { act, renderHook } from '@testing-library/react-hooks'; import { coreMock } from '@kbn/core/public/mocks'; -import * as rulesApi from '../../../detections/containers/detection_engine/rules/api'; +import * as rulesApi from '../../rule_management/api/api'; import * as listsApi from '@kbn/securitysolution-list-api'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; -import { savedRuleMock } from '../../../detections/containers/detection_engine/rules/mock'; +import { savedRuleMock } from '../../rule_management/logic/mock'; import type { ExceptionListType, ListArray, @@ -26,7 +26,7 @@ import type { import { useFetchOrCreateRuleExceptionList } from './use_fetch_or_create_rule_exception_list'; const mockKibanaHttpService = coreMock.createStart().http; -jest.mock('../../../detections/containers/detection_engine/rules/api'); +jest.mock('../../rule_management/api/api'); jest.mock('@kbn/securitysolution-list-api'); describe('useFetchOrCreateRuleExceptionList', () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.tsx index ff7883ba910cb..a42ecbf586440 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.tsx @@ -20,11 +20,8 @@ import { import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; import type { HttpStart } from '@kbn/core/public'; -import type { Rule } from '../../../detections/containers/detection_engine/rules/types'; -import { - fetchRuleById, - patchRule, -} from '../../../detections/containers/detection_engine/rules/api'; +import type { Rule } from '../../rule_management/logic/types'; +import { fetchRuleById, patchRule } from '../../rule_management/api/api'; export type ReturnUseFetchOrCreateRuleExceptionList = [boolean, ExceptionListSchema | null]; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_find_references.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_find_references.tsx index a051f140ec2cb..b039f32c5e17f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_find_references.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_find_references.tsx @@ -7,10 +7,10 @@ import { useEffect, useRef, useState } from 'react'; -import type { ExceptionListRuleReferencesSchema } from '../../../../common/detection_engine/schemas/response'; -import { findRuleExceptionReferences } from '../../../detections/containers/detection_engine/rules/api'; +import type { ExceptionListRuleReferencesSchema } from '../../../../common/detection_engine/rule_exceptions'; +import { findRuleExceptionReferences } from '../../rule_management/api/api'; import { useToasts } from '../../../common/lib/kibana'; -import type { FindRulesReferencedByExceptionsListProp } from '../../../detections/containers/detection_engine/rules/types'; +import type { FindRulesReferencedByExceptionsListProp } from '../../rule_management/logic/types'; import * as i18n from '../utils/translations'; export interface RuleReferences { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/translations.ts index 8f189c7aaf7db..012f4e677a5b2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/translations.ts @@ -94,14 +94,14 @@ export const MODAL_ERROR_ACCORDION_TEXT = i18n.translate( } ); -export const DISSASOCIATE_LIST_SUCCESS = (id: string) => - i18n.translate('xpack.securitySolution.exceptions.dissasociateListSuccessText', { +export const DISASSOCIATE_LIST_SUCCESS = (id: string) => + i18n.translate('xpack.securitySolution.exceptions.disassociateListSuccessText', { values: { id }, defaultMessage: 'Exception list ({id}) has successfully been removed', }); -export const DISSASOCIATE_EXCEPTION_LIST_ERROR = i18n.translate( - 'xpack.securitySolution.exceptions.dissasociateExceptionListError', +export const DISASSOCIATE_EXCEPTION_LIST_ERROR = i18n.translate( + 'xpack.securitySolution.exceptions.disassociateExceptionListError', { defaultMessage: 'Failed to remove exception list', } diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/columns.tsx similarity index 91% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/columns.tsx index 2257d9b79905c..4b196968de008 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/columns.tsx @@ -10,12 +10,12 @@ import type { EuiBasicTableColumn } from '@elastic/eui'; import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; -import { DEFAULT_RELATIVE_DATE_THRESHOLD } from '../../../../../../../common/constants'; -import type { FormatUrl } from '../../../../../../common/components/link_to'; -import { PopoverItems } from '../../../../../../common/components/popover_items'; -import { FormattedRelativePreferenceDate } from '../../../../../../common/components/formatted_date'; -import { getRuleDetailsUrl } from '../../../../../../common/components/link_to/redirect_to_detection_engine'; -import { LinkAnchor } from '../../../../../../common/components/links'; +import { DEFAULT_RELATIVE_DATE_THRESHOLD } from '../../../../../common/constants'; +import type { FormatUrl } from '../../../../common/components/link_to'; +import { PopoverItems } from '../../../../common/components/popover_items'; +import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; +import { getRuleDetailsUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; +import { LinkAnchor } from '../../../../common/components/links'; import * as i18n from './translations'; import type { ExceptionListInfo } from './use_all_exception_lists'; import type { ExceptionsTableItem } from './types'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_search_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_search_bar.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_search_bar.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_search_bar.tsx diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.test.tsx similarity index 89% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.test.tsx index 5c1300ce377a2..c443968c14015 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.test.tsx @@ -8,18 +8,18 @@ import React from 'react'; import { mount } from 'enzyme'; -import { TestProviders } from '../../../../../../common/mock'; +import { TestProviders } from '../../../../common/mock'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; -import { useUserData } from '../../../../../components/user_info'; +import { useUserData } from '../../../../detections/components/user_info'; import { ExceptionListsTable } from './exceptions_table'; import { useApi, useExceptionLists } from '@kbn/securitysolution-list-hooks'; import { useAllExceptionLists } from './use_all_exception_lists'; import { useHistory } from 'react-router-dom'; -import { generateHistoryMock } from '../../../../../../common/utils/route/mocks'; +import { generateHistoryMock } from '../../../../common/utils/route/mocks'; -jest.mock('../../../../../components/user_info'); -jest.mock('../../../../../../common/lib/kibana'); +jest.mock('../../../../detections/components/user_info'); +jest.mock('../../../../common/lib/kibana'); jest.mock('./use_all_exception_lists'); jest.mock('@kbn/securitysolution-list-hooks'); jest.mock('react-router-dom', () => { @@ -39,7 +39,7 @@ jest.mock('@kbn/i18n-react', () => { }; }); -jest.mock('../../../../../containers/detection_engine/lists/use_lists_config', () => ({ +jest.mock('../../../../detections/containers/detection_engine/lists/use_lists_config', () => ({ useListsConfig: jest.fn().mockReturnValue({ loading: false }), })); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.tsx similarity index 91% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.tsx index d7908d0bbce66..b2d3d078abb54 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table.tsx @@ -19,28 +19,29 @@ import { import type { NamespaceType, ExceptionListFilter } from '@kbn/securitysolution-io-ts-list-types'; import { useApi, useExceptionLists } from '@kbn/securitysolution-list-hooks'; -import { useAppToasts } from '../../../../../../common/hooks/use_app_toasts'; -import { AutoDownload } from '../../../../../../common/components/auto_download/auto_download'; -import { useKibana } from '../../../../../../common/lib/kibana'; -import { useFormatUrl } from '../../../../../../common/components/link_to'; -import { Loader } from '../../../../../../common/components/loader'; + +import { AutoDownload } from '../../../../common/components/auto_download/auto_download'; +import { useFormatUrl } from '../../../../common/components/link_to'; +import { Loader } from '../../../../common/components/loader'; +import { useKibana } from '../../../../common/lib/kibana'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { hasUserCRUDPermission } from '../../../../common/utils/privileges'; import * as i18n from './translations'; import { ExceptionsTableUtilityBar } from './exceptions_table_utility_bar'; import type { AllExceptionListsColumns } from './columns'; import { getAllExceptionListsColumns } from './columns'; import { useAllExceptionLists } from './use_all_exception_lists'; -import { ReferenceErrorModal } from '../../../../../components/value_lists_management_flyout/reference_error_modal'; -import { patchRule } from '../../../../../containers/detection_engine/rules/api'; +import { ReferenceErrorModal } from '../../../../detections/components/value_lists_management_flyout/reference_error_modal'; +import { patchRule } from '../../../rule_management/api/api'; import { ExceptionsSearchBar } from './exceptions_search_bar'; -import { getSearchFilters } from '../helpers'; -import { SecurityPageName } from '../../../../../../../common/constants'; -import { useUserData } from '../../../../../components/user_info'; -import { userHasPermissions } from '../../helpers'; -import { useListsConfig } from '../../../../../containers/detection_engine/lists/use_lists_config'; +import { getSearchFilters } from '../../../rule_management_ui/components/rules_table/helpers'; +import { SecurityPageName } from '../../../../../common/constants'; +import { useUserData } from '../../../../detections/components/user_info'; +import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; import type { ExceptionsTableItem } from './types'; -import { MissingPrivilegesCallOut } from '../../../../../components/callouts/missing_privileges_callout'; -import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../../../../common/endpoint/service/artifacts/constants'; +import { MissingPrivilegesCallOut } from '../../../../detections/components/callouts/missing_privileges_callout'; +import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../../common/endpoint/service/artifacts/constants'; export type Func = () => Promise<void>; @@ -63,7 +64,7 @@ const exceptionReferenceModalInitialState: ReferenceModalState = { export const ExceptionListsTable = React.memo(() => { const { formatUrl } = useFormatUrl(SecurityPageName.rules); const [{ loading: userInfoLoading, canUserCRUD, canUserREAD }] = useUserData(); - const hasPermissions = userHasPermissions(canUserCRUD); + const hasPermissions = hasUserCRUDPermission(canUserCRUD); const { loading: listsConfigLoading } = useListsConfig(); const loading = userInfoLoading || listsConfigLoading; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table_utility_bar.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table_utility_bar.test.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table_utility_bar.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table_utility_bar.test.tsx index d2bf2b8547f68..f40e0d66cb492 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table_utility_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table_utility_bar.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { TestProviders } from '../../../../../../common/mock'; +import { TestProviders } from '../../../../common/mock'; import { render, screen, within } from '@testing-library/react'; import { ExceptionsTableUtilityBar } from './exceptions_table_utility_bar'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table_utility_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table_utility_bar.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table_utility_bar.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table_utility_bar.tsx index 062b4b0fef8f9..98ac0bf25d8ec 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table_utility_bar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table_utility_bar.tsx @@ -13,7 +13,7 @@ import { UtilityBarGroup, UtilityBarSection, UtilityBarText, -} from '../../../../../../common/components/utility_bar'; +} from '../../../../common/components/utility_bar'; import * as i18n from './translations'; interface ExceptionsTableUtilityBarProps { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/translations.ts diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/types.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/types.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/types.ts diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/use_all_exception_lists.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/use_all_exception_lists.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/use_all_exception_lists.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/use_all_exception_lists.tsx index f48de2459fea7..cd77e72722132 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/use_all_exception_lists.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions_ui/pages/exceptions/use_all_exception_lists.tsx @@ -8,8 +8,8 @@ import { useCallback, useEffect, useState } from 'react'; import type { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; -import type { Rule } from '../../../../../containers/detection_engine/rules'; -import { fetchRules } from '../../../../../containers/detection_engine/rules/api'; +import type { Rule } from '../../../rule_management/logic'; +import { fetchRules } from '../../../rule_management/api/api'; export interface ExceptionListInfo extends ExceptionListSchema { rules: Rule[]; } diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/__mocks__/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/__mocks__/api.ts similarity index 56% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/__mocks__/api.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/api/__mocks__/api.ts index e445e5b935af2..e02e0c83dfe70 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/__mocks__/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/__mocks__/api.ts @@ -5,11 +5,9 @@ * 2.0. */ -import type { FullResponseSchema } from '../../../../../../common/detection_engine/schemas/request'; -import type { GetInstalledIntegrationsResponse } from '../../../../../../common/detection_engine/schemas/response'; - -import { getRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; -import { savedRuleMock, rulesMock } from '../mock'; +import type { RuleResponse } from '../../../../../common/detection_engine/rule_schema'; +import { getRulesSchemaMock } from '../../../../../common/detection_engine/rule_schema/mocks'; +import { savedRuleMock, rulesMock } from '../../logic/mock'; import type { PatchRuleProps, @@ -21,18 +19,18 @@ import type { FetchRuleProps, FetchRulesResponse, FetchRulesProps, -} from '../types'; +} from '../../logic/types'; -export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise<FullResponseSchema> => +export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise<RuleResponse> => Promise.resolve(getRulesSchemaMock()); -export const createRule = async ({ rule, signal }: CreateRulesProps): Promise<FullResponseSchema> => +export const createRule = async ({ rule, signal }: CreateRulesProps): Promise<RuleResponse> => Promise.resolve(getRulesSchemaMock()); export const patchRule = async ({ ruleProperties, signal, -}: PatchRuleProps): Promise<FullResponseSchema> => Promise.resolve(getRulesSchemaMock()); +}: PatchRuleProps): Promise<RuleResponse> => Promise.resolve(getRulesSchemaMock()); export const getPrePackagedRulesStatus = async ({ signal, @@ -62,30 +60,4 @@ export const fetchRules = async (_: FetchRulesProps): Promise<FetchRulesResponse export const fetchTags = async ({ signal }: { signal: AbortSignal }): Promise<string[]> => Promise.resolve(['elastic', 'love', 'quality', 'code']); -// do not delete -export const fetchInstalledIntegrations = async ({ - packages, - signal, -}: { - packages?: string[]; - signal?: AbortSignal; -}): Promise<GetInstalledIntegrationsResponse> => { - return Promise.resolve({ - installed_integrations: [ - { - package_name: 'atlassian_bitbucket', - package_title: 'Atlassian Bitbucket', - package_version: '1.0.1', - integration_name: 'audit', - integration_title: 'Audit Logs', - is_enabled: true, - }, - { - package_name: 'system', - package_title: 'System', - package_version: '1.6.4', - is_enabled: true, - }, - ], - }); -}; +export const performBulkAction = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts similarity index 97% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts index 2511e8da834f0..ca84045e06c65 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts @@ -6,7 +6,18 @@ */ import { buildEsQuery } from '@kbn/es-query'; -import { KibanaServices } from '../../../../common/lib/kibana'; +import { KibanaServices } from '../../../common/lib/kibana'; + +import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '../../../../common/detection_engine/rule_exceptions'; +import { getPatchRulesSchemaMock } from '../../../../common/detection_engine/rule_management/mocks'; +import { + getCreateRulesSchemaMock, + getUpdateRulesSchemaMock, + getRulesSchemaMock, +} from '../../../../common/detection_engine/rule_schema/mocks'; + +import { rulesMock } from '../logic/mock'; +import type { FindRulesReferencedByExceptionsListProp } from '../logic/types'; import { createRule, @@ -22,19 +33,10 @@ import { previewRule, findRuleExceptionReferences, } from './api'; -import { getRulesSchemaMock } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; -import { - getCreateRulesSchemaMock, - getUpdateRulesSchemaMock, -} from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; -import { getPatchRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/patch_rules_schema.mock'; -import { rulesMock } from './mock'; -import type { FindRulesReferencedByExceptionsListProp } from './types'; -import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '../../../../../common/constants'; const abortCtrl = new AbortController(); const mockKibanaServices = KibanaServices.get as jest.Mock; -jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../common/lib/kibana'); const fetchMock = jest.fn(); mockKibanaServices.mockReturnValue({ http: { fetch: fetchMock } }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts similarity index 69% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index e2c1e0d31cd57..3fd5fd65bb639 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -6,64 +6,65 @@ */ import { camelCase } from 'lodash'; -import type { HttpStart } from '@kbn/core/public'; - import type { CreateRuleExceptionListItemSchema, ExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; + +import type { BulkActionsDryRunErrCode } from '../../../../common/constants'; import { - DETECTION_ENGINE_RULES_URL, - DETECTION_ENGINE_PREPACKAGED_URL, - DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL, - DETECTION_ENGINE_TAGS_URL, DETECTION_ENGINE_RULES_BULK_ACTION, DETECTION_ENGINE_RULES_PREVIEW, - DETECTION_ENGINE_INSTALLED_INTEGRATIONS_URL, + DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_RULES_URL_FIND, - DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL, -} from '../../../../../common/constants'; -import type { BulkAction } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; + DETECTION_ENGINE_TAGS_URL, +} from '../../../../common/constants'; + +import { + PREBUILT_RULES_STATUS_URL, + PREBUILT_RULES_URL, +} from '../../../../common/detection_engine/prebuilt_rules'; + +import type { RulesReferencedByExceptionListsSchema } from '../../../../common/detection_engine/rule_exceptions'; +import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '../../../../common/detection_engine/rule_exceptions'; + +import type { BulkActionEditPayload } from '../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkAction } from '../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; + import type { - FullResponseSchema, + RuleResponse, PreviewResponse, -} from '../../../../../common/detection_engine/schemas/request'; -import type { - GetInstalledIntegrationsResponse, - RulesReferencedByExceptionListsSchema, -} from '../../../../../common/detection_engine/schemas/response'; +} from '../../../../common/detection_engine/rule_schema'; +import { KibanaServices } from '../../../common/lib/kibana'; +import * as i18n from '../../../detections/pages/detection_engine/rules/translations'; import type { - UpdateRulesProps, CreateRulesProps, + ExportDocumentsProps, + FetchRuleProps, FetchRulesProps, FetchRulesResponse, - Rule, - FetchRuleProps, + FindRulesReferencedByExceptionsProps, ImportDataProps, - ExportDocumentsProps, ImportDataResponse, - PrePackagedRulesStatusResponse, PatchRuleProps, - BulkActionProps, - BulkActionResponseMap, + PrePackagedRulesStatusResponse, PreviewRulesProps, - FindRulesReferencedByExceptionsProps, -} from './types'; -import { KibanaServices } from '../../../../common/lib/kibana'; -import * as i18n from '../../../pages/detection_engine/rules/translations'; -import { convertRulesFilterToKQL } from './utils'; + Rule, + UpdateRulesProps, +} from '../logic/types'; +import { convertRulesFilterToKQL } from '../logic/utils'; /** * Create provided Rule * - * @param rule CreateRulesSchema to add + * @param rule RuleCreateProps to add * @param signal to cancel request * * @throws An error if response is not OK */ -export const createRule = async ({ rule, signal }: CreateRulesProps): Promise<FullResponseSchema> => - KibanaServices.get().http.fetch<FullResponseSchema>(DETECTION_ENGINE_RULES_URL, { +export const createRule = async ({ rule, signal }: CreateRulesProps): Promise<RuleResponse> => + KibanaServices.get().http.fetch<RuleResponse>(DETECTION_ENGINE_RULES_URL, { method: 'POST', body: JSON.stringify(rule), signal, @@ -72,13 +73,13 @@ export const createRule = async ({ rule, signal }: CreateRulesProps): Promise<Fu /** * Update provided Rule using PUT * - * @param rule UpdateRulesSchema to be updated + * @param rule RuleUpdateProps to be updated * @param signal to cancel request * * @throws An error if response is not OK */ -export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise<FullResponseSchema> => - KibanaServices.get().http.fetch<FullResponseSchema>(DETECTION_ENGINE_RULES_URL, { +export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise<RuleResponse> => + KibanaServices.get().http.fetch<RuleResponse>(DETECTION_ENGINE_RULES_URL, { method: 'PUT', body: JSON.stringify(rule), signal, @@ -98,8 +99,8 @@ export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise<Fu export const patchRule = async ({ ruleProperties, signal, -}: PatchRuleProps): Promise<FullResponseSchema> => - KibanaServices.get().http.fetch<FullResponseSchema>(DETECTION_ENGINE_RULES_URL, { +}: PatchRuleProps): Promise<RuleResponse> => + KibanaServices.get().http.fetch<RuleResponse>(DETECTION_ENGINE_RULES_URL, { method: 'PATCH', body: JSON.stringify(ruleProperties), signal, @@ -108,7 +109,7 @@ export const patchRule = async ({ /** * Preview provided Rule * - * @param rule CreateRulesSchema to add + * @param rule RuleCreateProps to add * @param signal to cancel request * * @throws An error if response is not OK @@ -177,28 +178,49 @@ export const fetchRules = async ({ * @throws An error if response is not OK */ export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise<Rule> => - pureFetchRuleById({ id, http: KibanaServices.get().http, signal }); - -/** - * Fetch a Rule by providing a Rule ID - * - * @param id Rule ID's (not rule_id) - * @param http Kibana http service - * @param signal to cancel request - * - * @throws An error if response is not OK - */ -export const pureFetchRuleById = async ({ - id, - http, - signal, -}: FetchRuleProps & { http: HttpStart }): Promise<Rule> => - http.fetch<Rule>(DETECTION_ENGINE_RULES_URL, { + KibanaServices.get().http.fetch<Rule>(DETECTION_ENGINE_RULES_URL, { method: 'GET', query: { id }, signal, }); +export interface BulkActionSummary { + failed: number; + succeeded: number; + total: number; +} + +export interface BulkActionResult { + updated: Rule[]; + created: Rule[]; + deleted: Rule[]; +} + +export interface BulkActionAggregatedError { + message: string; + status_code: number; + err_code?: BulkActionsDryRunErrCode; + rules: Array<{ id: string; name?: string }>; +} + +export interface BulkActionResponse { + success?: boolean; + rules_count?: number; + attributes: { + summary: BulkActionSummary; + results: BulkActionResult; + errors?: BulkActionAggregatedError[]; + }; +} + +export interface BulkActionProps { + action: Exclude<BulkAction, BulkAction.export>; + query?: string; + ids?: string[]; + edit?: BulkActionEditPayload[]; + isDryRun?: boolean; +} + /** * Perform bulk action with rules selected by a filter query * @@ -210,48 +232,75 @@ export const pureFetchRuleById = async ({ * * @throws An error if response is not OK */ -export const performBulkAction = async <Action extends BulkAction>({ +export const performBulkAction = async ({ action, query, edit, ids, isDryRun, -}: BulkActionProps<Action>): Promise<BulkActionResponseMap<Action>> => - KibanaServices.get().http.fetch<BulkActionResponseMap<Action>>( - DETECTION_ENGINE_RULES_BULK_ACTION, - { - method: 'POST', - body: JSON.stringify({ - action, - ...(edit ? { edit } : {}), - ...(ids ? { ids } : {}), - ...(query !== undefined ? { query } : {}), - }), - query: { - ...(isDryRun ? { dry_run: isDryRun } : {}), - }, - } - ); +}: BulkActionProps): Promise<BulkActionResponse> => + KibanaServices.get().http.fetch<BulkActionResponse>(DETECTION_ENGINE_RULES_BULK_ACTION, { + method: 'POST', + body: JSON.stringify({ + action, + ...(edit ? { edit } : {}), + ...(ids ? { ids } : {}), + ...(query !== undefined ? { query } : {}), + }), + query: { + ...(isDryRun ? { dry_run: isDryRun } : {}), + }, + }); + +export interface BulkExportProps { + query?: string; + ids?: string[]; +} + +export type BulkExportResponse = Blob; /** - * Create Prepackaged Rules + * Bulk export rules selected by a filter query * - * @param signal AbortSignal for cancelling request + * @param query filter query to select rules to perform bulk action with + * @param ids string[] rule ids to select rules to perform bulk action with * * @throws An error if response is not OK */ -export const createPrepackagedRules = async (): Promise<{ +export const bulkExportRules = async ({ + query, + ids, +}: BulkExportProps): Promise<BulkExportResponse> => + KibanaServices.get().http.fetch<BulkExportResponse>(DETECTION_ENGINE_RULES_BULK_ACTION, { + method: 'POST', + body: JSON.stringify({ + action: BulkAction.export, + ...(ids ? { ids } : {}), + ...(query !== undefined ? { query } : {}), + }), + }); + +export interface CreatePrepackagedRulesResponse { rules_installed: number; rules_updated: number; timelines_installed: number; timelines_updated: number; -}> => { +} + +/** + * Create Prepackaged Rules + * + * @param signal AbortSignal for cancelling request + * + * @throws An error if response is not OK + */ +export const createPrepackagedRules = async (): Promise<CreatePrepackagedRulesResponse> => { const result = await KibanaServices.get().http.fetch<{ rules_installed: number; rules_updated: number; timelines_installed: number; timelines_updated: number; - }>(DETECTION_ENGINE_PREPACKAGED_URL, { + }>(PREBUILT_RULES_URL, { method: 'PUT', }); @@ -320,6 +369,8 @@ export const exportRules = async ({ }); }; +export type FetchTagsResponse = string[]; + /** * Fetch all unique Tags used by Rules * @@ -327,8 +378,8 @@ export const exportRules = async ({ * * @throws An error if response is not OK */ -export const fetchTags = async ({ signal }: { signal: AbortSignal }): Promise<string[]> => - KibanaServices.get().http.fetch<string[]>(DETECTION_ENGINE_TAGS_URL, { +export const fetchTags = async ({ signal }: { signal?: AbortSignal }): Promise<FetchTagsResponse> => + KibanaServices.get().http.fetch<FetchTagsResponse>(DETECTION_ENGINE_TAGS_URL, { method: 'GET', signal, }); @@ -345,39 +396,10 @@ export const getPrePackagedRulesStatus = async ({ }: { signal: AbortSignal | undefined; }): Promise<PrePackagedRulesStatusResponse> => - KibanaServices.get().http.fetch<PrePackagedRulesStatusResponse>( - DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL, - { - method: 'GET', - signal, - } - ); - -/** - * Fetch all installed integrations - * - * @param packages array of packages to filter for - * @param signal to cancel request - * - * @throws An error if response is not OK - */ -export const fetchInstalledIntegrations = async ({ - packages, - signal, -}: { - packages?: string[]; - signal?: AbortSignal; -}): Promise<GetInstalledIntegrationsResponse> => - KibanaServices.get().http.fetch<GetInstalledIntegrationsResponse>( - DETECTION_ENGINE_INSTALLED_INTEGRATIONS_URL, - { - method: 'GET', - query: { - packages: packages?.sort()?.join(','), - }, - signal, - } - ); + KibanaServices.get().http.fetch<PrePackagedRulesStatusResponse>(PREBUILT_RULES_STATUS_URL, { + method: 'GET', + signal, + }); /** * Fetch info on what exceptions lists are referenced by what rules diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/__mocks__/mock_react_query_response.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/__mocks__/mock_react_query_response.ts new file mode 100644 index 0000000000000..8a5728bac5462 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/__mocks__/mock_react_query_response.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 type { UseQueryResult } from '@tanstack/react-query'; + +export const mockReactQueryResponse = <TData>(result: Partial<UseQueryResult<TData>>) => ({ + isLoading: false, + error: null, + isError: false, + isLoadingError: false, + isRefetchError: false, + isSuccess: true, + status: 'success', + dataUpdatedAt: 0, + errorUpdatedAt: 0, + failureCount: 0, + errorUpdateCount: 0, + isFetched: true, + isFetchedAfterMount: true, + isFetching: false, + isPaused: false, + isPlaceholderData: false, + isPreviousData: false, + isRefetching: false, + isStale: false, + refetch: jest.fn(), + remove: jest.fn(), + fetchStatus: 'idle', + data: undefined, + ...result, +}); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/__mocks__/use_prebuilt_rules_status_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/__mocks__/use_prebuilt_rules_status_query.ts new file mode 100644 index 0000000000000..ea8c6b53a35b5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/__mocks__/use_prebuilt_rules_status_query.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PrePackagedRulesStatusResponse } from '../../../logic'; +import { mockReactQueryResponse } from './mock_react_query_response'; + +export const usePrebuiltRulesStatusQuery = jest.fn(() => + mockReactQueryResponse<PrePackagedRulesStatusResponse>({ + data: { + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: 0, + timelines_installed: 0, + timelines_not_installed: 0, + timelines_not_updated: 0, + }, + }) +); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/constants.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/constants.ts new file mode 100644 index 0000000000000..61e0d1e05f7f0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/constants.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. + */ + +const ONE_MINUTE = 60000; + +export const DEFAULT_QUERY_OPTIONS = { + refetchIntervalInBackground: false, + staleTime: ONE_MINUTE * 5, +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.ts new file mode 100644 index 0000000000000..e52a5cd8e0618 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_action_mutation.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. + */ +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; +import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import type { BulkActionProps, BulkActionResponse } from '../api'; +import { performBulkAction } from '../api'; +import { useInvalidateFetchPrebuiltRulesStatusQuery } from './use_fetch_prebuilt_rules_status_query'; +import { useInvalidateFindRulesQuery, useUpdateRulesCache } from './use_find_rules_query'; +import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query'; +import { useInvalidateFetchRuleByIdQuery } from './use_fetch_rule_by_id_query'; + +export const useBulkActionMutation = ( + options?: UseMutationOptions<BulkActionResponse, Error, BulkActionProps> +) => { + const invalidateFindRulesQuery = useInvalidateFindRulesQuery(); + const invalidateFetchRuleByIdQuery = useInvalidateFetchRuleByIdQuery(); + const invalidateFetchTagsQuery = useInvalidateFetchTagsQuery(); + const invalidateFetchPrebuiltRulesStatusQuery = useInvalidateFetchPrebuiltRulesStatusQuery(); + const updateRulesCache = useUpdateRulesCache(); + + return useMutation<BulkActionResponse, Error, BulkActionProps>( + (action: BulkActionProps) => performBulkAction(action), + { + ...options, + onSuccess: (...args) => { + const [res, { action }] = args; + switch (action) { + case BulkAction.enable: + case BulkAction.disable: { + invalidateFetchRuleByIdQuery(); + // This action doesn't affect rule content, no need for invalidation + updateRulesCache(res?.attributes?.results?.updated ?? []); + break; + } + case BulkAction.delete: + invalidateFindRulesQuery(); + invalidateFetchRuleByIdQuery(); + invalidateFetchTagsQuery(); + invalidateFetchPrebuiltRulesStatusQuery(); + break; + case BulkAction.duplicate: + invalidateFindRulesQuery(); + invalidateFetchPrebuiltRulesStatusQuery(); + break; + case BulkAction.edit: + updateRulesCache(res?.attributes?.results?.updated ?? []); + invalidateFetchRuleByIdQuery(); + invalidateFetchTagsQuery(); + break; + } + + if (options?.onSuccess) { + options.onSuccess(...args); + } + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_export_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_export_mutation.ts new file mode 100644 index 0000000000000..bcc5fbcdbb18e --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_export_mutation.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; +import type { BulkExportProps, BulkExportResponse } from '../api'; +import { bulkExportRules } from '../api'; + +export const useBulkExportMutation = ( + options?: UseMutationOptions<BulkExportResponse, Error, BulkExportProps> +) => { + return useMutation<BulkExportResponse, Error, BulkExportProps>( + (action: BulkExportProps) => bulkExportRules(action), + options + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_prebuilt_rules_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_prebuilt_rules_mutation.ts new file mode 100644 index 0000000000000..2559be0609d08 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_prebuilt_rules_mutation.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 { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; +import type { CreatePrepackagedRulesResponse } from '../api'; +import { createPrepackagedRules } from '../api'; +import { useInvalidateFetchPrebuiltRulesStatusQuery } from './use_fetch_prebuilt_rules_status_query'; +import { useInvalidateFindRulesQuery } from './use_find_rules_query'; +import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query'; + +export const useCreatePrebuiltRulesMutation = ( + options?: UseMutationOptions<CreatePrepackagedRulesResponse> +) => { + const invalidateFindRulesQuery = useInvalidateFindRulesQuery(); + const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery(); + const invalidateFetchTagsQuery = useInvalidateFetchTagsQuery(); + + return useMutation(() => createPrepackagedRules(), { + ...options, + onSuccess: (...args) => { + // Always invalidate all rules and the prepackaged rules status cache as + // the number of rules might change after the installation + invalidatePrePackagedRulesStatus(); + invalidateFindRulesQuery(); + invalidateFetchTagsQuery(); + + if (options?.onSuccess) { + options.onSuccess(...args); + } + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_rule_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_rule_mutation.ts new file mode 100644 index 0000000000000..8d62927a6261f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_create_rule_mutation.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; +import type { + RuleCreateProps, + RuleResponse, +} from '../../../../../common/detection_engine/rule_schema'; +import { transformOutput } from '../../../../detections/containers/detection_engine/rules/transforms'; +import { createRule } from '../api'; +import { useInvalidateFetchPrebuiltRulesStatusQuery } from './use_fetch_prebuilt_rules_status_query'; +import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query'; +import { useInvalidateFindRulesQuery } from './use_find_rules_query'; + +export const useCreateRuleMutation = ( + options?: UseMutationOptions<RuleResponse, Error, RuleCreateProps> +) => { + const invalidateFindRulesQuery = useInvalidateFindRulesQuery(); + const invalidateFetchTagsQuery = useInvalidateFetchTagsQuery(); + const invalidateFetchPrePackagedRulesStatusQuery = useInvalidateFetchPrebuiltRulesStatusQuery(); + + return useMutation<RuleResponse, Error, RuleCreateProps>( + (rule: RuleCreateProps) => createRule({ rule: transformOutput(rule) }), + { + ...options, + onSuccess: (...args) => { + invalidateFetchPrePackagedRulesStatusQuery(); + invalidateFindRulesQuery(); + invalidateFetchTagsQuery(); + + if (options?.onSuccess) { + options.onSuccess(...args); + } + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_prebuilt_rules_status_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_prebuilt_rules_status_query.ts new file mode 100644 index 0000000000000..a0344386ffe04 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_prebuilt_rules_status_query.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 { useCallback } from 'react'; +import type { UseQueryOptions } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { getPrePackagedRulesStatus } from '../api'; +import { DEFAULT_QUERY_OPTIONS } from './constants'; +import type { PrePackagedRulesStatusResponse } from '../../logic'; + +export const PREBUILT_RULES_STATUS_QUERY_KEY = 'prePackagedRulesStatus'; + +export const useFetchPrebuiltRulesStatusQuery = ( + options: UseQueryOptions<PrePackagedRulesStatusResponse> +) => { + return useQuery<PrePackagedRulesStatusResponse>( + [PREBUILT_RULES_STATUS_QUERY_KEY], + async ({ signal }) => { + const response = await getPrePackagedRulesStatus({ signal }); + return response; + }, + { + ...DEFAULT_QUERY_OPTIONS, + ...options, + } + ); +}; + +/** + * We should use this hook to invalidate the prepackaged rules cache. For + * example, rule mutations that affect rule set size, like creation or deletion, + * should lead to cache invalidation. + * + * @returns A rules cache invalidation callback + */ +export const useInvalidateFetchPrebuiltRulesStatusQuery = () => { + const queryClient = useQueryClient(); + + return useCallback(() => { + queryClient.invalidateQueries([PREBUILT_RULES_STATUS_QUERY_KEY], { + refetchType: 'active', + }); + }, [queryClient]); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts new file mode 100644 index 0000000000000..03fe7c6e2df17 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { UseQueryOptions } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useCallback } from 'react'; +import { transformInput } from '../../../../detections/containers/detection_engine/rules/transforms'; +import type { Rule } from '../../logic'; +import { fetchRuleById } from '../api'; +import { DEFAULT_QUERY_OPTIONS } from './constants'; + +const FIND_ONE_RULE_QUERY_KEY = 'findOneRule'; + +/** + * A wrapper around useQuery provides default values to the underlying query, + * like query key, abortion signal, and error handler. + * + * @param id - rule's id, not rule_id + * @param options - react-query options + * @returns useQuery result + */ +export const useFetchRuleByIdQuery = (id: string, options: UseQueryOptions<Rule>) => { + return useQuery<Rule>( + [FIND_ONE_RULE_QUERY_KEY, id], + async ({ signal }) => { + const response = await fetchRuleById({ signal, id }); + + return transformInput(response); + }, + { + ...DEFAULT_QUERY_OPTIONS, + ...options, + } + ); +}; + +/** + * We should use this hook to invalidate the rules cache. For example, rule + * mutations that affect rule set size, like creation or deletion, should lead + * to cache invalidation. + * + * @returns A rules cache invalidation callback + */ +export const useInvalidateFetchRuleByIdQuery = () => { + const queryClient = useQueryClient(); + + return useCallback(() => { + queryClient.invalidateQueries([FIND_ONE_RULE_QUERY_KEY], { + refetchType: 'active', + }); + }, [queryClient]); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_tags_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_tags_query.ts new file mode 100644 index 0000000000000..1be43f992f07f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_tags_query.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 { UseQueryOptions } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useCallback } from 'react'; +import type { FetchTagsResponse } from '../api'; +import { fetchTags } from '../api'; +import { DEFAULT_QUERY_OPTIONS } from './constants'; + +// TODO: https://github.com/elastic/kibana/pull/142950 Let's use more detailed cache keys, e.g. ['GET', DETECTION_ENGINE_TAGS_URL] +const TAGS_QUERY_KEY = 'tags'; + +/** + * Hook for using the list of Tags from the Detection Engine API + * + */ +export const useFetchTagsQuery = (options?: UseQueryOptions<FetchTagsResponse>) => { + return useQuery<FetchTagsResponse>( + [TAGS_QUERY_KEY], + async ({ signal }) => { + return fetchTags({ signal }); + }, + { + ...DEFAULT_QUERY_OPTIONS, + ...options, + } + ); +}; + +export const useInvalidateFetchTagsQuery = () => { + const queryClient = useQueryClient(); + + return useCallback(() => { + queryClient.invalidateQueries([TAGS_QUERY_KEY], { + refetchType: 'active', + }); + }, [queryClient]); +}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_find_rules_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_find_rules_query.ts similarity index 85% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_find_rules_query.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_find_rules_query.ts index 523a05012ca19..ad50ab471a7fd 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_find_rules_query.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_find_rules_query.ts @@ -5,13 +5,12 @@ * 2.0. */ -import { useCallback } from 'react'; import type { UseQueryOptions } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { fetchRules } from './api'; -import * as i18n from './translations'; -import type { FilterOptions, PaginationOptions, Rule, SortingOptions } from './types'; +import { useCallback } from 'react'; +import type { FilterOptions, PaginationOptions, Rule, SortingOptions } from '../../logic'; +import { fetchRules } from '../api'; +import { DEFAULT_QUERY_OPTIONS } from './constants'; export interface FindRulesQueryArgs { filterOptions?: FilterOptions; @@ -21,14 +20,14 @@ export interface FindRulesQueryArgs { const FIND_RULES_QUERY_KEY = 'findRules'; -export interface RulesQueryData { +export interface RulesQueryResponse { rules: Rule[]; total: number; } /** * A wrapper around useQuery provides default values to the underlying query, - * like query key, abortion signal, and error handler. + * like query key, abortion signal. * * @param queryPrefix - query prefix used to differentiate the query from other * findRules queries @@ -37,27 +36,23 @@ export interface RulesQueryData { * @returns useQuery result */ export const useFindRulesQuery = ( - queryPrefix: string[], queryArgs: FindRulesQueryArgs, queryOptions: UseQueryOptions< - RulesQueryData, + RulesQueryResponse, Error, - RulesQueryData, + RulesQueryResponse, [...string[], FindRulesQueryArgs] > ) => { - const { addError } = useAppToasts(); - return useQuery( - [FIND_RULES_QUERY_KEY, ...queryPrefix, queryArgs], + [FIND_RULES_QUERY_KEY, queryArgs], async ({ signal }) => { const response = await fetchRules({ signal, ...queryArgs }); return { rules: response.data, total: response.total }; }, { - refetchIntervalInBackground: false, - onError: (error: Error) => addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }), + ...DEFAULT_QUERY_OPTIONS, ...queryOptions, } ); @@ -70,7 +65,7 @@ export const useFindRulesQuery = ( * * @returns A rules cache invalidation callback */ -export const useInvalidateRules = () => { +export const useInvalidateFindRulesQuery = () => { const queryClient = useQueryClient(); return useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts new file mode 100644 index 0000000000000..6f15fb4fdd8ce --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { UseMutationOptions } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; +import type { + RuleResponse, + RuleUpdateProps, +} from '../../../../../common/detection_engine/rule_schema'; +import { transformOutput } from '../../../../detections/containers/detection_engine/rules/transforms'; +import { updateRule } from '../api'; +import { useInvalidateFindRulesQuery } from './use_find_rules_query'; +import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query'; +import { useInvalidateFetchRuleByIdQuery } from './use_fetch_rule_by_id_query'; + +export const useUpdateRuleMutation = ( + options?: UseMutationOptions<RuleResponse, Error, RuleUpdateProps> +) => { + const invalidateFindRulesQuery = useInvalidateFindRulesQuery(); + const invalidateFetchTagsQuery = useInvalidateFetchTagsQuery(); + const invalidateFetchRuleByIdQuery = useInvalidateFetchRuleByIdQuery(); + + return useMutation<RuleResponse, Error, RuleUpdateProps>( + (rule: RuleUpdateProps) => updateRule({ rule: transformOutput(rule) }), + { + ...options, + onSuccess: (...args) => { + invalidateFindRulesQuery(); + invalidateFetchRuleByIdQuery(); + invalidateFetchTagsQuery(); + + if (options?.onSuccess) { + options.onSuccess(...args); + } + }, + } + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/__mocks__/use_bulk_export.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/__mocks__/use_bulk_export.ts new file mode 100644 index 0000000000000..c72ba1fab45f9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/__mocks__/use_bulk_export.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 useBulkExport = jest.fn(() => ({ + bulkExport: jest.fn(), +})); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/__mocks__/use_execute_bulk_action.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/__mocks__/use_execute_bulk_action.ts new file mode 100644 index 0000000000000..8e8ad47288027 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/__mocks__/use_execute_bulk_action.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const useExecuteBulkAction = jest.fn(() => ({ + executeBulkAction: jest.fn(), +})); + +export const goToRuleEditPage = jest.fn(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts new file mode 100644 index 0000000000000..314811d0b142d --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/translations.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { ErrorToastOptions } from '@kbn/core-notifications-browser'; +import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; +import type { BulkActionSummary } from '../../api/api'; + +export function getErrorToastContent( + action: BulkAction, + summary: BulkActionSummary +): ErrorToastOptions { + let title: string; + let toastMessage: string | undefined; + + switch (action) { + case BulkAction.export: + title = i18n.RULES_BULK_EXPORT_FAILURE; + if (summary) { + toastMessage = i18n.RULES_BULK_EXPORT_FAILURE_DESCRIPTION(summary.failed); + } + break; + case BulkAction.duplicate: + title = i18n.RULES_BULK_DUPLICATE_FAILURE; + if (summary) { + toastMessage = i18n.RULES_BULK_DUPLICATE_FAILURE_DESCRIPTION(summary.failed); + } + break; + case BulkAction.delete: + title = i18n.RULES_BULK_DELETE_FAILURE; + if (summary) { + toastMessage = i18n.RULES_BULK_DELETE_FAILURE_DESCRIPTION(summary.failed); + } + break; + case BulkAction.enable: + title = i18n.RULES_BULK_ENABLE_FAILURE; + if (summary) { + toastMessage = i18n.RULES_BULK_ENABLE_FAILURE_DESCRIPTION(summary.failed); + } + break; + case BulkAction.disable: + title = i18n.RULES_BULK_DISABLE_FAILURE; + if (summary) { + toastMessage = i18n.RULES_BULK_DISABLE_FAILURE_DESCRIPTION(summary.failed); + } + break; + case BulkAction.edit: + title = i18n.RULES_BULK_EDIT_FAILURE; + if (summary) { + toastMessage = i18n.RULES_BULK_EDIT_FAILURE_DESCRIPTION(summary.failed); + } + break; + } + + return { title, toastMessage }; +} + +export function getSuccessToastContent(action: BulkAction, summary: BulkActionSummary) { + let title: string; + let text: string | undefined; + + switch (action) { + case BulkAction.export: + title = i18n.RULES_BULK_EXPORT_SUCCESS; + text = getExportSuccessToastMessage(summary.succeeded, summary.total); + break; + case BulkAction.duplicate: + title = i18n.RULES_BULK_DUPLICATE_SUCCESS; + text = i18n.RULES_BULK_DUPLICATE_SUCCESS_DESCRIPTION(summary.succeeded); + break; + case BulkAction.delete: + title = i18n.RULES_BULK_DELETE_SUCCESS; + text = i18n.RULES_BULK_DELETE_SUCCESS_DESCRIPTION(summary.succeeded); + break; + case BulkAction.enable: + title = i18n.RULES_BULK_ENABLE_SUCCESS; + text = i18n.RULES_BULK_ENABLE_SUCCESS_DESCRIPTION(summary.succeeded); + break; + case BulkAction.disable: + title = i18n.RULES_BULK_DISABLE_SUCCESS; + text = i18n.RULES_BULK_DISABLE_SUCCESS_DESCRIPTION(summary.succeeded); + break; + case BulkAction.edit: + title = i18n.RULES_BULK_EDIT_SUCCESS; + text = i18n.RULES_BULK_EDIT_SUCCESS_DESCRIPTION(summary.succeeded); + break; + } + + return { title, text }; +} + +const getExportSuccessToastMessage = (succeeded: number, total: number) => { + const message = [i18n.RULES_BULK_EXPORT_SUCCESS_DESCRIPTION(succeeded, total)]; + + // if not all rules are successfully exported it means there included prebuilt rules + // display message to users that prebuilt rules were excluded + if (total > succeeded) { + message.push(i18n.RULES_BULK_EXPORT_PREBUILT_RULES_EXCLUDED_DESCRIPTION); + } + + return message.join(' '); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.ts new file mode 100644 index 0000000000000..793580e5d3be0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_bulk_export.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 { useCallback } from 'react'; +import type { BulkActionResponse, BulkActionSummary } from '..'; +import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import type { HTTPError } from '../../../../../common/detection_engine/types'; +import type { UseAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { downloadBlob } from '../../../../common/utils/download_blob'; +import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; +import { getExportedRulesCounts } from '../../../rule_management_ui/components/rules_table/helpers'; +import type { RulesTableActions } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; +import { useBulkExportMutation } from '../../api/hooks/use_bulk_export_mutation'; +import { getErrorToastContent, getSuccessToastContent } from './translations'; + +interface RulesBulkActionArgs { + visibleRuleIds?: string[]; + search: { query: string } | { ids: string[] }; + setLoadingRules?: RulesTableActions['setLoadingRules']; +} + +export const useBulkExport = () => { + const toasts = useAppToasts(); + const { mutateAsync } = useBulkExportMutation(); + + const bulkExport = useCallback( + async ({ visibleRuleIds = [], setLoadingRules, search }: RulesBulkActionArgs) => { + try { + setLoadingRules?.({ ids: visibleRuleIds, action: BulkAction.export }); + return await mutateAsync(search); + } catch (error) { + defaultErrorHandler(toasts, error); + } finally { + setLoadingRules?.({ ids: [], action: null }); + } + }, + [mutateAsync, toasts] + ); + + return { bulkExport }; +}; + +/** + * downloads exported rules, received from export action + * @param params.response - Blob results with exported rules + * @param params.toasts - {@link UseAppToasts} toasts service + * @param params.onSuccess - {@link OnActionSuccessCallback} optional toast to display when action successful + * @param params.onError - {@link OnActionErrorCallback} optional toast to display when action failed + */ +export async function downloadExportedRules({ + response, + toasts, +}: { + response: Blob; + toasts: UseAppToasts; +}) { + try { + downloadBlob(response, `${i18n.EXPORT_FILENAME}.ndjson`); + defaultSuccessHandler(toasts, await getExportedRulesCounts(response)); + } catch (error) { + defaultErrorHandler(toasts, error); + } +} + +function defaultErrorHandler(toasts: UseAppToasts, error: HTTPError): void { + const summary = (error?.body as BulkActionResponse)?.attributes?.summary; + error.stack = JSON.stringify(error.body, null, 2); + + toasts.addError(error, getErrorToastContent(BulkAction.export, summary)); +} + +function defaultSuccessHandler(toasts: UseAppToasts, summary: BulkActionSummary): void { + toasts.addSuccess(getSuccessToastContent(BulkAction.export, summary)); +} diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.ts new file mode 100644 index 0000000000000..3ba9e353f3fcd --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action.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 { NavigateToAppOptions } from '@kbn/core/public'; +import { useCallback } from 'react'; +import type { BulkActionResponse, BulkActionSummary } from '..'; +import { APP_UI_ID } from '../../../../../common/constants'; +import type { BulkActionEditPayload } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import type { HTTPError } from '../../../../../common/detection_engine/types'; +import { SecurityPageName } from '../../../../app/types'; +import { getEditRuleUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; +import type { UseAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../../common/lib/telemetry'; +import type { RulesTableActions } from '../../../rule_management_ui/components/rules_table/rules_table/rules_table_context'; +import { useBulkActionMutation } from '../../api/hooks/use_bulk_action_mutation'; +import { getErrorToastContent, getSuccessToastContent } from './translations'; + +export const goToRuleEditPage = ( + ruleId: string, + navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise<void> +) => { + navigateToApp(APP_UI_ID, { + deepLinkId: SecurityPageName.rules, + path: getEditRuleUrl(ruleId ?? ''), + }); +}; + +type OnActionSuccessCallback = ( + toasts: UseAppToasts, + action: BulkAction, + summary: BulkActionSummary +) => void; + +type OnActionErrorCallback = (toasts: UseAppToasts, action: BulkAction, error: HTTPError) => void; + +interface RulesBulkActionArgs { + action: Exclude<BulkAction, BulkAction.export>; + visibleRuleIds?: string[]; + search: { query: string } | { ids: string[] }; + payload?: { edit?: BulkActionEditPayload[] }; + onError?: OnActionErrorCallback; + onFinish?: () => void; + onSuccess?: OnActionSuccessCallback; + setLoadingRules?: RulesTableActions['setLoadingRules']; +} + +export const useExecuteBulkAction = () => { + const toasts = useAppToasts(); + const { mutateAsync } = useBulkActionMutation(); + + const executeBulkAction = useCallback( + async ({ + visibleRuleIds = [], + action, + setLoadingRules, + search, + payload, + onSuccess = defaultSuccessHandler, + onError = defaultErrorHandler, + onFinish, + }: RulesBulkActionArgs) => { + try { + setLoadingRules?.({ ids: visibleRuleIds, action }); + const response = await mutateAsync({ ...search, action, edit: payload?.edit }); + sendTelemetry(action, response); + onSuccess(toasts, action, response.attributes.summary); + + return response; + } catch (error) { + onError(toasts, action, error); + } finally { + setLoadingRules?.({ ids: [], action: null }); + onFinish?.(); + } + }, + [mutateAsync, toasts] + ); + + return { executeBulkAction }; +}; + +function defaultErrorHandler(toasts: UseAppToasts, action: BulkAction, error: HTTPError) { + const summary = (error?.body as BulkActionResponse)?.attributes?.summary; + error.stack = JSON.stringify(error.body, null, 2); + + toasts.addError(error, getErrorToastContent(action, summary)); +} + +async function defaultSuccessHandler( + toasts: UseAppToasts, + action: BulkAction, + summary: BulkActionSummary +) { + toasts.addSuccess(getSuccessToastContent(action, summary)); +} + +function sendTelemetry(action: BulkAction, response: BulkActionResponse) { + if (action === BulkAction.disable || action === BulkAction.enable) { + if (response.attributes.results.updated.some((rule) => rule.immutable)) { + track( + METRIC_TYPE.COUNT, + action === BulkAction.enable + ? TELEMETRY_EVENT.SIEM_RULE_ENABLED + : TELEMETRY_EVENT.SIEM_RULE_DISABLED + ); + } + if (response.attributes.results.updated.some((rule) => !rule.immutable)) { + track( + METRIC_TYPE.COUNT, + action === BulkAction.disable + ? TELEMETRY_EVENT.CUSTOM_RULE_ENABLED + : TELEMETRY_EVENT.CUSTOM_RULE_DISABLED + ); + } + } +} diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/index.ts similarity index 66% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/index.ts index 42c1eff7435bc..8caaade79ee40 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/index.ts @@ -5,9 +5,10 @@ * 2.0. */ -export * from './api'; +// TODO: https://github.com/elastic/kibana/pull/142950 delete this whole index file +// TODO: https://github.com/elastic/kibana/pull/142950 delete api re-export +export * from '../api/api'; export * from './use_update_rule'; export * from './use_create_rule'; export * from './types'; export * from './use_rule'; -export * from './use_pre_packaged_rules'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/mock.ts similarity index 99% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/mock.ts index bdcc8e0ec5e8a..6e1c53cb2da21 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/mock.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/mock.ts @@ -7,6 +7,7 @@ import type { FetchRulesResponse, Rule } from './types'; +// TODO move to __mocks__ export const savedRuleMock: Rule = { author: [], actions: [], diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/translations.ts new file mode 100644 index 0000000000000..36735f1ff11e1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/translations.ts @@ -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 { i18n } from '@kbn/i18n'; + +export const RULE_AND_TIMELINE_FETCH_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.rulesAndTimelines', + { + defaultMessage: 'Failed to fetch Rules and Timelines', + } +); + +export const RULE_ADD_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.addRuleFailDescription', + { + defaultMessage: 'Failed to add Rule', + } +); + +export const RULE_AND_TIMELINE_PREPACKAGED_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.createPrePackagedRuleAndTimelineFailDescription', + { + defaultMessage: 'Failed to installed pre-packaged rules and timelines from elastic', + } +); + +export const RULE_AND_TIMELINE_PREPACKAGED_SUCCESS = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.createPrePackagedRuleAndTimelineSuccesDescription', + { + defaultMessage: 'Installed pre-packaged rules and timeline templates from elastic', + } +); + +export const RULE_PREPACKAGED_SUCCESS = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.createPrePackagedRuleSuccesDescription', + { + defaultMessage: 'Installed pre-packaged rules from elastic', + } +); + +export const TIMELINE_PREPACKAGED_SUCCESS = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.createPrePackagedTimelineSuccesDescription', + { + defaultMessage: 'Installed pre-packaged timeline templates from elastic', + } +); + +export const TAG_FETCH_FAILURE = i18n.translate( + 'xpack.securitySolution.containers.detectionEngine.tagFetchFailDescription', + { + defaultMessage: 'Failed to fetch Tags', + } +); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts similarity index 58% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index 842353a6799f8..fa2b7f16ce9f7 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -7,56 +7,74 @@ import * as t from 'io-ts'; -import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; -import { listArray } from '@kbn/securitysolution-io-ts-list-types'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { - risk_score_mapping, - threat_query, + RiskScore, + RiskScoreMapping, + RuleActionArray, + RuleActionThrottle, + RuleInterval, + RuleIntervalFrom, + RuleIntervalTo, + Severity, + SeverityMapping, + threat_filters, threat_index, threat_indicator_path, - threat_mapping, threat_language, - threat_filters, - threats, + threat_mapping, + threat_query, type, - severity_mapping, - severity, } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; -import { RuleExecutionSummary } from '../../../../../common/detection_engine/rule_monitoring'; - -import type { SortOrder } from '../../../../../common/detection_engine/schemas/common'; -import type { - BulkActionEditPayload, - BulkAction, -} from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import { RuleExecutionSummary } from '../../../../common/detection_engine/rule_monitoring'; import { - alias_purpose as savedObjectResolveAliasPurpose, - outcome as savedObjectResolveOutcome, - author, - building_block_type, - license, - rule_name_override, - data_view_id, - timestamp_override, - timestamp_override_fallback_disabled, - timestamp_field, - event_category_override, - tiebreaker_field, - threshold, + AlertsIndex, + BuildingBlockType, + DataViewId, + EventCategoryOverride, + ExceptionListArray, + IndexPatternArray, + InvestigationGuide, + IsRuleEnabled, + IsRuleImmutable, + MaxSignals, RelatedIntegrationArray, RequiredFieldArray, + RuleAuthorArray, + RuleDescription, + RuleFalsePositiveArray, + RuleFilterArray, + RuleLicense, + RuleName, + RuleNameOverride, + RuleObjectId, + RuleQuery, + RuleReferenceArray, + RuleSignatureId, + RuleTagArray, + RuleVersion, + SavedObjectResolveAliasPurpose, + SavedObjectResolveAliasTargetId, + SavedObjectResolveOutcome, SetupGuide, -} from '../../../../../common/detection_engine/schemas/common'; - + ThreatArray, + Threshold, + TiebreakerField, + TimelineTemplateId, + TimelineTemplateTitle, + TimestampField, + TimestampOverride, + TimestampOverrideFallbackDisabled, +} from '../../../../common/detection_engine/rule_schema'; + +import type { PatchRuleRequestBody } from '../../../../common/detection_engine/rule_management'; import type { - CreateRulesSchema, - PatchRulesSchema, - UpdateRulesSchema, -} from '../../../../../common/detection_engine/schemas/request'; - -import type { BulkActionsDryRunErrCode } from '../../../../../common/constants'; + RuleCreateProps, + RuleUpdateProps, +} from '../../../../common/detection_engine/rule_schema'; +import type { SortOrder } from '../../../../common/detection_engine/schemas/common'; /** * Params is an "record", since it is a type of RuleActionParams which is action templates. @@ -73,23 +91,23 @@ export const action = t.exact( ); export interface CreateRulesProps { - rule: CreateRulesSchema; - signal: AbortSignal; + rule: RuleCreateProps; + signal?: AbortSignal; } export interface PreviewRulesProps { - rule: CreateRulesSchema & { invocationCount: number; timeframeEnd: string }; - signal: AbortSignal; + rule: RuleCreateProps & { invocationCount: number; timeframeEnd: string }; + signal?: AbortSignal; } export interface UpdateRulesProps { - rule: UpdateRulesSchema; - signal: AbortSignal; + rule: RuleUpdateProps; + signal?: AbortSignal; } export interface PatchRuleProps { - ruleProperties: PatchRulesSchema; - signal: AbortSignal; + ruleProperties: PatchRuleRequestBody; + signal?: AbortSignal; } const MetaRule = t.intersection([ @@ -105,73 +123,73 @@ const MetaRule = t.intersection([ // TODO: make a ticket export const RuleSchema = t.intersection([ t.type({ - author, + author: RuleAuthorArray, created_at: t.string, created_by: t.string, - description: t.string, - enabled: t.boolean, - false_positives: t.array(t.string), - from: t.string, - id: t.string, - interval: t.string, - immutable: t.boolean, - name: t.string, - max_signals: t.number, - references: t.array(t.string), + description: RuleDescription, + enabled: IsRuleEnabled, + false_positives: RuleFalsePositiveArray, + from: RuleIntervalFrom, + id: RuleObjectId, + interval: RuleInterval, + immutable: IsRuleImmutable, + name: RuleName, + max_signals: MaxSignals, + references: RuleReferenceArray, related_integrations: RelatedIntegrationArray, required_fields: RequiredFieldArray, - risk_score: t.number, - risk_score_mapping, - rule_id: t.string, - severity, - severity_mapping, + risk_score: RiskScore, + risk_score_mapping: RiskScoreMapping, + rule_id: RuleSignatureId, + severity: Severity, + severity_mapping: SeverityMapping, setup: SetupGuide, - tags: t.array(t.string), + tags: RuleTagArray, type, - to: t.string, - threat: threats, + to: RuleIntervalTo, + threat: ThreatArray, updated_at: t.string, updated_by: t.string, - actions: t.array(action), - throttle: t.union([t.string, t.null]), + actions: RuleActionArray, + throttle: t.union([RuleActionThrottle, t.null]), }), t.partial({ - outcome: savedObjectResolveOutcome, - alias_target_id: t.string, - alias_purpose: savedObjectResolveAliasPurpose, - building_block_type, + outcome: SavedObjectResolveOutcome, + alias_target_id: SavedObjectResolveAliasTargetId, + alias_purpose: SavedObjectResolveAliasPurpose, + building_block_type: BuildingBlockType, anomaly_threshold: t.number, - filters: t.array(t.unknown), - index: t.array(t.string), - data_view_id, + filters: RuleFilterArray, + index: IndexPatternArray, + data_view_id: DataViewId, language: t.string, - license, + license: RuleLicense, meta: MetaRule, machine_learning_job_id: t.array(t.string), new_terms_fields: t.array(t.string), history_window_start: t.string, - output_index: t.string, - query: t.string, - rule_name_override, + output_index: AlertsIndex, + query: RuleQuery, + rule_name_override: RuleNameOverride, saved_id: t.string, - threshold, + threshold: Threshold, threat_query, threat_filters, threat_index, threat_indicator_path, threat_mapping, threat_language, - timeline_id: t.string, - timeline_title: t.string, - timestamp_override, - timestamp_override_fallback_disabled, - timestamp_field, - event_category_override, - tiebreaker_field, - note: t.string, - exceptions_list: listArray, + timeline_id: TimelineTemplateId, + timeline_title: TimelineTemplateTitle, + timestamp_override: TimestampOverride, + timestamp_override_fallback_disabled: TimestampOverrideFallbackDisabled, + event_category_override: EventCategoryOverride, + timestamp_field: TimestampField, + tiebreaker_field: TiebreakerField, + note: InvestigationGuide, + exceptions_list: ExceptionListArray, uuid: t.string, - version: t.number, + version: RuleVersion, execution_summary: RuleExecutionSummary, }), ]); @@ -230,55 +248,9 @@ export interface FetchRulesResponse { export interface FetchRuleProps { id: string; - signal: AbortSignal; -} - -export interface BulkActionProps<Action extends BulkAction> { - action: Action; - query?: string; - ids?: string[]; - edit?: BulkActionEditPayload[]; - isDryRun?: boolean; -} - -export interface BulkActionSummary { - failed: number; - succeeded: number; - total: number; -} - -export interface BulkActionResult { - updated: Rule[]; - created: Rule[]; - deleted: Rule[]; -} - -export interface BulkActionAggregatedError { - message: string; - status_code: number; - err_code?: BulkActionsDryRunErrCode; - rules: Array<{ id: string; name?: string }>; -} - -export interface BulkActionResponse { - success?: boolean; - rules_count?: number; - attributes: { - summary: BulkActionSummary; - results: BulkActionResult; - errors?: BulkActionAggregatedError[]; - }; + signal?: AbortSignal; } -export type BulkActionResponseMap<Action extends BulkAction> = { - [BulkAction.delete]: BulkActionResponse; - [BulkAction.disable]: BulkActionResponse; - [BulkAction.enable]: BulkActionResponse; - [BulkAction.duplicate]: BulkActionResponse; - [BulkAction.export]: Blob; - [BulkAction.edit]: BulkActionResponse; -}[Action]; - export interface BasicFetchProps { signal: AbortSignal; } diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_create_pre_packaged_rules.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_create_pre_packaged_rules.ts new file mode 100644 index 0000000000000..c1de79159918f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_create_pre_packaged_rules.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 { useCallback } from 'react'; +import { useUserData } from '../../../detections/components/user_info'; +import { useInstallPrePackagedRules } from './use_install_pre_packaged_rules'; + +export const useCreatePrePackagedRules = () => { + const [{ isSignalIndexExists, isAuthenticated, hasEncryptionKey, canUserCRUD, hasIndexWrite }] = + useUserData(); + const { mutateAsync: installPrePackagedRules, isLoading } = useInstallPrePackagedRules(); + + const canCreatePrePackagedRules = + canUserCRUD && hasIndexWrite && isAuthenticated && hasEncryptionKey && isSignalIndexExists; + + const createPrePackagedRules = useCallback(async () => { + if (canCreatePrePackagedRules) { + await installPrePackagedRules(); + } + }, [canCreatePrePackagedRules, installPrePackagedRules]); + + return { + isLoading, + createPrePackagedRules, + canCreatePrePackagedRules, + }; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_create_rule.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_create_rule.ts new file mode 100644 index 0000000000000..65cb3a8bc0460 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_create_rule.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. + */ + +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { useCreateRuleMutation } from '../api/hooks/use_create_rule_mutation'; +import * as i18n from './translations'; + +export const useCreateRule = () => { + const { addError } = useAppToasts(); + + return useCreateRuleMutation({ + onError: (error) => { + addError(error, { title: i18n.RULE_ADD_FAILURE }); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_disassociate_exception_list.ts similarity index 78% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_disassociate_exception_list.ts index ecb9b7c095786..67a30233c59d7 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_disassociate_exception_list.ts @@ -9,12 +9,12 @@ import { useEffect, useState, useRef } from 'react'; import type { List } from '@kbn/securitysolution-io-ts-list-types'; import type { HttpStart } from '@kbn/core/public'; -import { patchRule } from './api'; +import { patchRule } from '../api/api'; type Func = (lists: List[]) => void; -export type ReturnUseDissasociateExceptionList = [boolean, Func | null]; +export type ReturnUseDisassociateExceptionList = [boolean, Func | null]; -export interface UseDissasociateExceptionListProps { +export interface UseDisassociateExceptionListProps { http: HttpStart; ruleRuleId: string; onError: (arg: Error) => void; @@ -30,20 +30,20 @@ export interface UseDissasociateExceptionListProps { * @param onSuccess success callback * */ -export const useDissasociateExceptionList = ({ +export const useDisassociateExceptionList = ({ http, ruleRuleId, onError, onSuccess, -}: UseDissasociateExceptionListProps): ReturnUseDissasociateExceptionList => { +}: UseDisassociateExceptionListProps): ReturnUseDisassociateExceptionList => { const [isLoading, setLoading] = useState(false); - const dissasociateList = useRef<Func | null>(null); + const disassociateList = useRef<Func | null>(null); useEffect(() => { let isSubscribed = true; const abortCtrl = new AbortController(); - const dissasociateListFromRule = + const disassociateListFromRule = (id: string) => async (exceptionLists: List[]): Promise<void> => { try { @@ -69,7 +69,7 @@ export const useDissasociateExceptionList = ({ } }; - dissasociateList.current = dissasociateListFromRule(ruleRuleId); + disassociateList.current = disassociateListFromRule(ruleRuleId); return (): void => { isSubscribed = false; @@ -77,5 +77,5 @@ export const useDissasociateExceptionList = ({ }; }, [http, ruleRuleId, onError, onSuccess]); - return [isLoading, dissasociateList.current]; + return [isLoading, disassociateList.current]; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_dissasociate_exception_list.test.ts similarity index 67% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_dissasociate_exception_list.test.ts index 1a40d260b476c..9671dac1039a1 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_dissasociate_exception_list.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_dissasociate_exception_list.test.ts @@ -9,17 +9,17 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { coreMock } from '@kbn/core/public/mocks'; -import * as api from './api'; -import { getRulesSchemaMock } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; +import * as api from '../api/api'; +import { getRulesSchemaMock } from '../../../../common/detection_engine/rule_schema/mocks'; import type { - ReturnUseDissasociateExceptionList, - UseDissasociateExceptionListProps, -} from './use_dissasociate_exception_list'; -import { useDissasociateExceptionList } from './use_dissasociate_exception_list'; + ReturnUseDisassociateExceptionList, + UseDisassociateExceptionListProps, +} from './use_disassociate_exception_list'; +import { useDisassociateExceptionList } from './use_disassociate_exception_list'; const mockKibanaHttpService = coreMock.createStart().http; -describe('useDissasociateExceptionList', () => { +describe('useDisassociateExceptionList', () => { const onError = jest.fn(); const onSuccess = jest.fn(); @@ -34,10 +34,10 @@ describe('useDissasociateExceptionList', () => { test('initializes hook', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook< - UseDissasociateExceptionListProps, - ReturnUseDissasociateExceptionList + UseDisassociateExceptionListProps, + ReturnUseDisassociateExceptionList >(() => - useDissasociateExceptionList({ + useDisassociateExceptionList({ http: mockKibanaHttpService, ruleRuleId: 'rule_id', onError, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_find_rules.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_find_rules.ts new file mode 100644 index 0000000000000..653f1de8e3b3f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_find_rules.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 { UseQueryOptions } from '@tanstack/react-query'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import type { FindRulesQueryArgs } from '../api/hooks/use_find_rules_query'; +import { useFindRulesQuery } from '../api/hooks/use_find_rules_query'; +import * as i18n from './translations'; +import type { Rule } from './types'; + +export interface RulesQueryData { + rules: Rule[]; + total: number; +} + +/** + * A wrapper around useQuery provides default values to the underlying query, + * like query key, abortion signal, and error handler. + * + * @param requestArgs - fetch rules filters/pagination + * @param options - react-query options + * @returns useQuery result + */ +export const useFindRules = ( + requestArgs: FindRulesQueryArgs, + options: UseQueryOptions<RulesQueryData, Error, RulesQueryData, [...string[], FindRulesQueryArgs]> +) => { + const { addError } = useAppToasts(); + + return useFindRulesQuery(requestArgs, { + onError: (error: Error) => addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }), + ...options, + }); +}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_install_pre_packaged_rules.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_install_pre_packaged_rules.ts similarity index 63% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_install_pre_packaged_rules.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_install_pre_packaged_rules.ts index 3f56cac04fb02..21ea298986598 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_install_pre_packaged_rules.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_install_pre_packaged_rules.ts @@ -4,28 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useMutation } from '@tanstack/react-query'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { createPrepackagedRules } from './api'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { useCreatePrebuiltRulesMutation } from '../api/hooks/use_create_prebuilt_rules_mutation'; import * as i18n from './translations'; -import { useInvalidateRules } from './use_find_rules_query'; -import { useInvalidatePrePackagedRulesStatus } from './use_pre_packaged_rules_status'; export const useInstallPrePackagedRules = () => { const { addError, addSuccess } = useAppToasts(); - const invalidateRules = useInvalidateRules(); - const invalidatePrePackagedRulesStatus = useInvalidatePrePackagedRulesStatus(); - return useMutation(() => createPrepackagedRules(), { + return useCreatePrebuiltRulesMutation({ onError: (err) => { addError(err, { title: i18n.RULE_AND_TIMELINE_PREPACKAGED_FAILURE }); }, onSuccess: (result) => { addSuccess(getSuccessToastMessage(result)); - // Always invalidate all rules and the prepackaged rules status cache as - // the number of rules might change after the installation - invalidatePrePackagedRulesStatus(); - invalidateRules(); }, }); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_pre_packaged_rules_installation_status.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_pre_packaged_rules_installation_status.ts new file mode 100644 index 0000000000000..d45109ee078ed --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_pre_packaged_rules_installation_status.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 { getPrePackagedRuleInstallationStatus } from '../../../detections/pages/detection_engine/rules/helpers'; +import { usePrePackagedRulesStatus } from './use_pre_packaged_rules_status'; + +export const usePrePackagedRulesInstallationStatus = () => { + const { data: prePackagedRulesStatus } = usePrePackagedRulesStatus(); + + return getPrePackagedRuleInstallationStatus( + prePackagedRulesStatus?.rules_installed, + prePackagedRulesStatus?.rules_not_installed, + prePackagedRulesStatus?.rules_not_updated + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_pre_packaged_rules_status.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_pre_packaged_rules_status.ts new file mode 100644 index 0000000000000..889b670432d54 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_pre_packaged_rules_status.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 { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { useFetchPrebuiltRulesStatusQuery } from '../api/hooks/use_fetch_prebuilt_rules_status_query'; +import * as i18n from './translations'; + +export const usePrePackagedRulesStatus = () => { + const { addError } = useAppToasts(); + + return useFetchPrebuiltRulesStatusQuery({ + onError: (err) => { + addError(err, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_pre_packaged_timelines_installation_status.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_pre_packaged_timelines_installation_status.ts new file mode 100644 index 0000000000000..61933d625ba18 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_pre_packaged_timelines_installation_status.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 { getPrePackagedTimelineInstallationStatus } from '../../../detections/pages/detection_engine/rules/helpers'; +import { usePrePackagedRulesStatus } from './use_pre_packaged_rules_status'; + +export const usePrePackagedTimelinesInstallationStatus = () => { + const { data: prePackagedRulesStatus } = usePrePackagedRulesStatus(); + + return getPrePackagedTimelineInstallationStatus( + prePackagedRulesStatus?.timelines_installed, + prePackagedRulesStatus?.timelines_not_installed, + prePackagedRulesStatus?.timelines_not_updated + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule.ts new file mode 100644 index 0000000000000..234ba95de54d6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule.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 { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { useFetchRuleByIdQuery } from '../api/hooks/use_fetch_rule_by_id_query'; +import * as i18n from './translations'; + +/** + * Hook for using to get a Rule from the Detection Engine API + * + * @param id desired Rule ID's (not rule_id) + * + */ +export const useRule = (id: string) => { + const { addError } = useAppToasts(); + + return useFetchRuleByIdQuery(id, { + onError: (error) => { + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_indices.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_indices.test.ts similarity index 92% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_indices.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_indices.test.ts index d22b9a8d83220..b04cda31daf5f 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_indices.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_indices.test.ts @@ -8,9 +8,9 @@ import { renderHook } from '@testing-library/react-hooks'; import { useRuleIndices } from './use_rule_indices'; -import { useGetInstalledJob } from '../../../../common/components/ml/hooks/use_get_jobs'; +import { useGetInstalledJob } from '../../../common/components/ml/hooks/use_get_jobs'; -jest.mock('../../../../common/components/ml/hooks/use_get_jobs'); +jest.mock('../../../common/components/ml/hooks/use_get_jobs'); describe('useRuleIndices', () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_indices.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_indices.ts similarity index 90% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_indices.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_indices.ts index 77ffc49bb7413..4dbb28514255a 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_indices.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_indices.ts @@ -6,7 +6,7 @@ */ import { useMemo } from 'react'; -import { useGetInstalledJob } from '../../../../common/components/ml/hooks/use_get_jobs'; +import { useGetInstalledJob } from '../../../common/components/ml/hooks/use_get_jobs'; export const useRuleIndices = (machineLearningJobId?: string[], defaultRuleIndices?: string[]) => { const memoMlJobIds = useMemo(() => machineLearningJobId ?? [], [machineLearningJobId]); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_with_fallback.ts similarity index 79% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_with_fallback.ts index d832dd64cff70..6290c4ae6622f 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_rule_with_fallback.ts @@ -5,20 +5,18 @@ * 2.0. */ -import { useCallback, useEffect, useMemo } from 'react'; import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; -import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; import { isNotFoundError } from '@kbn/securitysolution-t-grid'; -import { expandDottedObject } from '../../../../../common/utils/expand_dotted'; - -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { AlertSearchResponse } from '../alerts/types'; -import { useQueryAlerts } from '../alerts/use_query'; -import { ALERTS_QUERY_NAMES } from '../alerts/constants'; -import { fetchRuleById } from './api'; -import { transformInput } from './transforms'; +import { useEffect, useMemo } from 'react'; +import { expandDottedObject } from '../../../../common/utils/expand_dotted'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { ALERTS_QUERY_NAMES } from '../../../detections/containers/detection_engine/alerts/constants'; +import type { AlertSearchResponse } from '../../../detections/containers/detection_engine/alerts/types'; +import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query'; +import { transformInput } from '../../../detections/containers/detection_engine/rules/transforms'; import * as i18n from './translations'; import type { Rule } from './types'; +import { useRule } from './use_rule'; interface UseRuleWithFallback { error: unknown; @@ -55,10 +53,6 @@ interface RACRule { }; } -const fetchWithOptionsSignal = withOptionalSignal(fetchRuleById); - -const useFetchRule = () => useAsync(fetchWithOptionsSignal); - const buildLastAlertQuery = (ruleId: string) => ({ query: { bool: { @@ -83,17 +77,9 @@ const buildLastAlertQuery = (ruleId: string) => ({ * In that case, try to fetch the latest alert generated by the rule and retrieve the rule data from the alert (fallback). */ export const useRuleWithFallback = (ruleId: string): UseRuleWithFallback => { - const { start, loading: ruleLoading, result: ruleData, error } = useFetchRule(); + const { isLoading: ruleLoading, data: ruleData, error, refetch } = useRule(ruleId); const { addError } = useAppToasts(); - const fetch = useCallback(() => { - start({ id: ruleId }); - }, [ruleId, start]); - - useEffect(() => { - fetch(); - }, [fetch]); - const isExistingRule = !isNotFoundError(error); const { loading: alertsLoading, data: alertsData } = useQueryAlerts<AlertHit, undefined>({ @@ -122,7 +108,7 @@ export const useRuleWithFallback = (ruleId: string): UseRuleWithFallback => { return { error, loading: ruleLoading || alertsLoading, - refresh: fetch, + refresh: refetch, rule: rule ?? null, isExistingRule, }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_tags.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_tags.ts new file mode 100644 index 0000000000000..ab3671f262f8a --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_tags.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 { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { useFetchTagsQuery } from '../api/hooks/use_fetch_tags_query'; +import * as i18n from './translations'; + +/** + * Hook for using the list of Tags from the Detection Engine API + * + */ +export const useTags = () => { + const { addError } = useAppToasts(); + + return useFetchTagsQuery({ + onError: (err) => { + addError(err, { title: i18n.TAG_FETCH_FAILURE }); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_update_rule.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_update_rule.ts new file mode 100644 index 0000000000000..900b3c30ccaa2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_update_rule.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. + */ + +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import { useUpdateRuleMutation } from '../api/hooks/use_update_rule_mutation'; +import * as i18n from './translations'; + +export const useUpdateRule = () => { + const { addError } = useAppToasts(); + + return useUpdateRuleMutation({ + onError: (error) => { + addError(error, { title: i18n.RULE_ADD_FAILURE }); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/utils.test.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.test.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/utils.test.ts diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/utils.ts similarity index 97% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/utils.ts index b4de6a95daaf1..67e64dab36a36 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { escapeKuery } from '../../../../common/lib/kuery'; +import { escapeKuery } from '../../../common/lib/kuery'; import type { FilterOptions } from './types'; const SEARCHABLE_RULE_PARAMS = [ diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts similarity index 89% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts index 967ba8a90d4f4..ac22095820255 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts @@ -6,12 +6,17 @@ */ import { FilterStateStore } from '@kbn/es-query'; -import type { Rule } from '../../../../../containers/detection_engine/rules'; -import type { AboutStepRule, ActionsStepRule, DefineStepRule, ScheduleStepRule } from '../../types'; -import { DataSourceType } from '../../types'; -import type { FieldValueQueryBar } from '../../../../../components/rules/query_bar'; -import { fillEmptySeverityMappings } from '../../helpers'; -import { getThreatMock } from '../../../../../../../common/detection_engine/schemas/types/threat.mock'; +import type { Rule } from '../../../../rule_management/logic'; +import type { + AboutStepRule, + ActionsStepRule, + DefineStepRule, + ScheduleStepRule, +} from '../../../../../detections/pages/detection_engine/rules/types'; +import { DataSourceType } from '../../../../../detections/pages/detection_engine/rules/types'; +import type { FieldValueQueryBar } from '../../../../../detections/components/rules/query_bar'; +import { fillEmptySeverityMappings } from '../../../../../detections/pages/detection_engine/rules/helpers'; +import { getThreatMock } from '../../../../../../common/detection_engine/schemas/types/threat.mock'; export const mockQueryBar: FieldValueQueryBar = { query: { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_action_dry_run_confirmation.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx similarity index 91% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_action_dry_run_confirmation.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx index 9207e0813ab70..941339be4fa31 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_action_dry_run_confirmation.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_dry_run_confirmation.tsx @@ -8,10 +8,10 @@ import React from 'react'; import { EuiConfirmModal } from '@elastic/eui'; -import * as i18n from '../../translations'; +import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; import { BulkActionRuleErrorsList } from './bulk_action_rule_errors_list'; -import { BulkAction } from '../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { assertUnreachable } from '../../../../../../../common/utility_types'; +import { BulkAction } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { assertUnreachable } from '../../../../../../common/utility_types'; import type { BulkActionForConfirmation, DryRunResult } from './types'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_action_rule_errors_list.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_action_rule_errors_list.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx index 987b244dd9fc6..758ccf0893e36 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_action_rule_errors_list.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.test.tsx @@ -11,9 +11,9 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { render, screen } from '@testing-library/react'; import { BulkActionRuleErrorsList } from './bulk_action_rule_errors_list'; -import { BulkActionsDryRunErrCode } from '../../../../../../../common/constants'; +import { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; import type { DryRunResult } from './types'; -import { BulkAction } from '../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import { BulkAction } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; const Wrapper: FC = ({ children }) => { return ( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_action_rule_errors_list.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_action_rule_errors_list.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx index 8f0275000a4ef..9789377f13ef3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_action_rule_errors_list.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_action_rule_errors_list.tsx @@ -9,8 +9,8 @@ import React from 'react'; import { EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { BulkActionsDryRunErrCode } from '../../../../../../../common/constants'; -import { BulkAction } from '../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; +import { BulkAction } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import type { DryRunResult, BulkActionForConfirmation } from './types'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_edit_flyout.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_edit_flyout.tsx similarity index 78% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_edit_flyout.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_edit_flyout.tsx index 194d4d90f860a..4f109fab7a8ba 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/bulk_edit_flyout.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_edit_flyout.tsx @@ -7,8 +7,8 @@ import React from 'react'; -import type { BulkActionEditPayload } from '../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { BulkActionEditType } from '../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import type { BulkActionEditPayload } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionEditType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { IndexPatternsForm } from './forms/index_patterns_form'; import { TagsForm } from './forms/tags_form'; @@ -21,10 +21,9 @@ interface BulkEditFlyoutProps { onConfirm: (bulkActionEditPayload: BulkActionEditPayload) => void; editAction: BulkActionEditType; rulesCount: number; - tags: string[]; } -const BulkEditFlyoutComponent = ({ editAction, tags, ...props }: BulkEditFlyoutProps) => { +const BulkEditFlyoutComponent = ({ editAction, ...props }: BulkEditFlyoutProps) => { switch (editAction) { case BulkActionEditType.add_index_patterns: case BulkActionEditType.delete_index_patterns: @@ -34,7 +33,7 @@ const BulkEditFlyoutComponent = ({ editAction, tags, ...props }: BulkEditFlyoutP case BulkActionEditType.add_tags: case BulkActionEditType.delete_tags: case BulkActionEditType.set_tags: - return <TagsForm {...props} editAction={editAction} tags={tags} />; + return <TagsForm {...props} editAction={editAction} />; case BulkActionEditType.set_timeline: return <TimelineTemplateForm {...props} />; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/bulk_edit_form_wrapper.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/bulk_edit_form_wrapper.tsx similarity index 91% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/bulk_edit_form_wrapper.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/bulk_edit_form_wrapper.tsx index a4fafd8d21bfd..364774465bd2d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/bulk_edit_form_wrapper.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/bulk_edit_form_wrapper.tsx @@ -21,10 +21,10 @@ import { EuiFlyoutBody, } from '@elastic/eui'; -import type { FormHook } from '../../../../../../../shared_imports'; -import { Form } from '../../../../../../../shared_imports'; +import type { FormHook } from '../../../../../../shared_imports'; +import { Form } from '../../../../../../shared_imports'; -import * as i18n from '../../../translations'; +import * as i18n from '../../../../../../detections/pages/detection_engine/rules/translations'; interface BulkEditFormWrapperProps { form: FormHook; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/index_patterns_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/index_patterns_form.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/index_patterns_form.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/index_patterns_form.tsx index adb19e397027b..2ea8b20dcd42c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/index_patterns_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/index_patterns_form.tsx @@ -9,15 +9,15 @@ import React from 'react'; import { EuiFormRow, EuiCallOut } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import * as i18n from '../../../translations'; +import * as i18n from '../../../../../../detections/pages/detection_engine/rules/translations'; -import { DEFAULT_INDEX_KEY } from '../../../../../../../../common/constants'; -import { useKibana } from '../../../../../../../common/lib/kibana'; +import { DEFAULT_INDEX_KEY } from '../../../../../../../common/constants'; +import { useKibana } from '../../../../../../common/lib/kibana'; -import { BulkActionEditType } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import type { BulkActionEditPayload } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import { BulkActionEditType } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import type { BulkActionEditPayload } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; -import type { FormSchema } from '../../../../../../../shared_imports'; +import type { FormSchema } from '../../../../../../shared_imports'; import { Field, getUseField, @@ -25,7 +25,7 @@ import { useForm, FIELD_TYPES, fieldValidators, -} from '../../../../../../../shared_imports'; +} from '../../../../../../shared_imports'; import { BulkEditFormWrapper } from './bulk_edit_form_wrapper'; const CommonUseField = getUseField({ component: Field }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/rule_actions_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/rule_actions_form.tsx similarity index 87% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/rule_actions_form.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/rule_actions_form.tsx index ec7d81e8b08a9..0bff754f20f4f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/rule_actions_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/rule_actions_form.tsx @@ -13,7 +13,7 @@ import type { ActionTypeRegistryContract, } from '@kbn/triggers-actions-ui-plugin/public'; -import type { FormSchema } from '../../../../../../../shared_imports'; +import type { FormSchema } from '../../../../../../shared_imports'; import { useForm, UseField, @@ -21,27 +21,27 @@ import { useFormData, getUseField, Field, -} from '../../../../../../../shared_imports'; -import { BulkActionEditType } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +} from '../../../../../../shared_imports'; +import { BulkActionEditType } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import type { BulkActionEditPayload, ThrottleForBulkActions, -} from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { NOTIFICATION_THROTTLE_RULE } from '../../../../../../../../common/constants'; +} from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { NOTIFICATION_THROTTLE_RULE } from '../../../../../../../common/constants'; import { BulkEditFormWrapper } from './bulk_edit_form_wrapper'; import { bulkAddRuleActions as i18n } from '../translations'; -import { useKibana } from '../../../../../../../common/lib/kibana'; +import { useKibana } from '../../../../../../common/lib/kibana'; import { ThrottleSelectField, THROTTLE_OPTIONS_FOR_BULK_RULE_ACTIONS, -} from '../../../../../../components/rules/throttle_select_field'; -import { getAllActionMessageParams } from '../../../helpers'; +} from '../../../../../../detections/components/rules/throttle_select_field'; +import { getAllActionMessageParams } from '../../../../../../detections/pages/detection_engine/rules/helpers'; -import { RuleActionsField } from '../../../../../../components/rules/rule_actions_field'; -import { validateRuleActionsField } from '../../../../../../containers/detection_engine/rules/validate_rule_actions_field'; +import { RuleActionsField } from '../../../../../../detections/components/rules/rule_actions_field'; +import { validateRuleActionsField } from '../../../../../../detections/containers/detection_engine/rules/validate_rule_actions_field'; const CommonUseField = getUseField({ component: Field }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/schedule_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/schedule_form.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/schedule_form.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/schedule_form.tsx index eb27aee61f93b..68e7a4ddfb2d3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/schedule_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/schedule_form.tsx @@ -5,16 +5,15 @@ * 2.0. */ -import React, { useCallback } from 'react'; import { EuiCallOut } from '@elastic/eui'; -import type { BulkActionEditPayload } from '../../../../../../../../common/detection_engine/schemas/request'; -import { BulkActionEditType } from '../../../../../../../../common/detection_engine/schemas/request'; -import { useForm, UseField } from '../../../../../../../shared_imports'; -import type { FormSchema } from '../../../../../../../shared_imports'; -import { BulkEditFormWrapper } from './bulk_edit_form_wrapper'; -import { ScheduleItem } from '../../../../../../components/rules/schedule_item_form'; - +import React, { useCallback } from 'react'; +import type { BulkActionEditPayload } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionEditType } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { ScheduleItem } from '../../../../../../detections/components/rules/schedule_item_form'; +import type { FormSchema } from '../../../../../../shared_imports'; +import { UseField, useForm } from '../../../../../../shared_imports'; import { bulkSetSchedule as i18n } from '../translations'; +import { BulkEditFormWrapper } from './bulk_edit_form_wrapper'; export interface ScheduleFormData { interval: string; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/tags_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/tags_form.tsx similarity index 87% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/tags_form.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/tags_form.tsx index e53469e27a09a..4288342c03d10 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/tags_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/tags_form.tsx @@ -5,26 +5,27 @@ * 2.0. */ -import React, { useMemo } from 'react'; -import { EuiFormRow, EuiCallOut } from '@elastic/eui'; +import { EuiCallOut, EuiFormRow } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useMemo } from 'react'; -import * as i18n from '../../../translations'; +import type { BulkActionEditPayload } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionEditType } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import * as i18n from '../../../../../../detections/pages/detection_engine/rules/translations'; import { caseInsensitiveSort } from '../../helpers'; -import type { BulkActionEditPayload } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { BulkActionEditType } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import type { FormSchema } from '../../../../../../../shared_imports'; +import type { FormSchema } from '../../../../../../shared_imports'; import { - useForm, Field, + fieldValidators, + FIELD_TYPES, getUseField, + useForm, useFormData, - FIELD_TYPES, - fieldValidators, -} from '../../../../../../../shared_imports'; +} from '../../../../../../shared_imports'; import { BulkEditFormWrapper } from './bulk_edit_form_wrapper'; +import { useTags } from '../../../../../rule_management/logic/use_tags'; type TagsEditActions = | BulkActionEditType.add_tags @@ -74,10 +75,10 @@ interface TagsFormProps { rulesCount: number; onClose: () => void; onConfirm: (bulkActionEditPayload: BulkActionEditPayload) => void; - tags: string[]; } -const TagsFormComponent = ({ editAction, rulesCount, onClose, onConfirm, tags }: TagsFormProps) => { +const TagsFormComponent = ({ editAction, rulesCount, onClose, onConfirm }: TagsFormProps) => { + const { data: tags = [] } = useTags(); const { form } = useForm({ defaultValue: initialFormData, schema, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/timeline_template_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/timeline_template_form.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/timeline_template_form.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/timeline_template_form.tsx index 6aa5a3100c100..4ad3d04af03d5 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/timeline_template_form.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/timeline_template_form.tsx @@ -8,11 +8,11 @@ import React, { useCallback } from 'react'; import { EuiCallOut } from '@elastic/eui'; -import type { FormSchema } from '../../../../../../../shared_imports'; -import { useForm, UseField } from '../../../../../../../shared_imports'; -import { PickTimeline } from '../../../../../../components/rules/pick_timeline'; -import type { BulkActionEditPayload } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { BulkActionEditType } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import type { FormSchema } from '../../../../../../shared_imports'; +import { useForm, UseField } from '../../../../../../shared_imports'; +import { PickTimeline } from '../../../../../../detections/components/rules/pick_timeline'; +import type { BulkActionEditPayload } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionEditType } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { BulkEditFormWrapper } from './bulk_edit_form_wrapper'; import { bulkApplyTimelineTemplate as i18n } from '../translations'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/translations.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/translations.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/translations.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/translations.tsx diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/types.ts similarity index 86% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/types.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/types.ts index d81c7c995a2fe..c8250269dbe61 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/types.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { BulkActionsDryRunErrCode } from '../../../../../../../common/constants'; -import type { BulkAction } from '../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import type { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; +import type { BulkAction } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; /** * Only 2 bulk actions are supported for for confirmation dry run modal: diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx similarity index 76% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx index 17443855a6abf..48e45e144e479 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx @@ -6,43 +6,39 @@ */ /* eslint-disable complexity */ -import React, { useCallback } from 'react'; import type { EuiContextMenuPanelDescriptor } from '@elastic/eui'; -import { EuiTextColor, EuiFlexGroup, EuiButton, EuiFlexItem } from '@elastic/eui'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { useIsMounted } from '@kbn/securitysolution-hook-utils'; - +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiTextColor } from '@elastic/eui'; import type { Toast } from '@kbn/core/public'; import { toMountPoint } from '@kbn/kibana-react-plugin/public'; -import type { BulkActionEditPayload } from '../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import { euiThemeVars } from '@kbn/ui-theme'; +import React, { useCallback } from 'react'; +import type { BulkActionEditPayload } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { BulkAction, BulkActionEditType, -} from '../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { isMlRule } from '../../../../../../../common/machine_learning/helpers'; -import { canEditRuleWithActions } from '../../../../../../common/utils/privileges'; -import { useRulesTableContext } from '../rules_table/rules_table_context'; -import * as detectionI18n from '../../../translations'; -import * as i18n from '../../translations'; -import { executeRulesBulkAction, downloadExportedRules } from '../actions'; +} from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { isMlRule } from '../../../../../../common/machine_learning/helpers'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; +import { BULK_RULE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; +import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; +import { canEditRuleWithActions } from '../../../../../common/utils/privileges'; +import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; +import * as detectionI18n from '../../../../../detections/pages/detection_engine/translations'; +import { + downloadExportedRules, + useBulkExport, +} from '../../../../rule_management/logic/bulk_actions/use_bulk_export'; +import { useExecuteBulkAction } from '../../../../rule_management/logic/bulk_actions/use_execute_bulk_action'; +import type { FilterOptions } from '../../../../rule_management/logic/types'; +import { convertRulesFilterToKQL } from '../../../../rule_management/logic/utils'; import { getExportedRulesDetails } from '../helpers'; +import { useRulesTableContext } from '../rules_table/rules_table_context'; import { useHasActionsPrivileges } from '../use_has_actions_privileges'; import { useHasMlPermissions } from '../use_has_ml_permissions'; -import { transformExportDetailsToDryRunResult } from './utils/dry_run_result'; +import type { BulkActionForConfirmation, DryRunResult } from './types'; import type { ExecuteBulkActionsDryRun } from './use_bulk_actions_dry_run'; -import { useAppToasts } from '../../../../../../common/hooks/use_app_toasts'; -import { convertRulesFilterToKQL } from '../../../../../containers/detection_engine/rules/utils'; +import { transformExportDetailsToDryRunResult } from './utils/dry_run_result'; import { prepareSearchParams } from './utils/prepare_search_params'; -import type { FilterOptions } from '../../../../../containers/detection_engine/rules/types'; -import { - useInvalidateRules, - useUpdateRulesCache, -} from '../../../../../containers/detection_engine/rules/use_find_rules_query'; -import { BULK_RULE_ACTIONS } from '../../../../../../common/lib/apm/user_actions'; -import { useStartTransaction } from '../../../../../../common/lib/apm/use_start_transaction'; -import { useInvalidatePrePackagedRulesStatus } from '../../../../../containers/detection_engine/rules/use_pre_packaged_rules_status'; - -import type { DryRunResult, BulkActionForConfirmation } from './types'; interface UseBulkActionsArgs { filterOptions: FilterOptions; @@ -54,7 +50,6 @@ interface UseBulkActionsArgs { completeBulkEditForm: ( bulkActionEditType: BulkActionEditType ) => Promise<BulkActionEditPayload | null>; - reFetchTags: () => void; executeBulkActionsDryRun: ExecuteBulkActionsDryRun; } @@ -63,33 +58,16 @@ export const useBulkActions = ({ confirmDeletion, showBulkActionConfirmation, completeBulkEditForm, - reFetchTags, executeBulkActionsDryRun, }: UseBulkActionsArgs) => { const hasMlPermissions = useHasMlPermissions(); const rulesTableContext = useRulesTableContext(); - const invalidateRules = useInvalidateRules(); - const updateRulesCache = useUpdateRulesCache(); - const invalidatePrePackagedRulesStatus = useInvalidatePrePackagedRulesStatus(); const hasActionsPrivileges = useHasActionsPrivileges(); const toasts = useAppToasts(); - const getIsMounted = useIsMounted(); const filterQuery = convertRulesFilterToKQL(filterOptions); const { startTransaction } = useStartTransaction(); - - // refetch tags if edit action is related to tags: add_tags/delete_tags/set_tags - const resolveTagsRefetch = useCallback( - async (bulkEditActionType: BulkActionEditType) => { - const isTagsAction = [ - BulkActionEditType.add_tags, - BulkActionEditType.set_tags, - BulkActionEditType.delete_tags, - ].includes(bulkEditActionType); - - return isTagsAction ? reFetchTags() : null; - }, - [reFetchTags] - ); + const { executeBulkAction } = useExecuteBulkAction(); + const { bulkExport } = useBulkExport(); const { state: { isAllSelected, rules, loadingRuleIds, selectedRuleIds }, @@ -125,14 +103,12 @@ export const useBulkActions = ({ ? disabledRules.map(({ id }) => id) : disabledRulesNoML.map(({ id }) => id); - const res = await executeRulesBulkAction({ + await executeBulkAction({ visibleRuleIds: ruleIds, action: BulkAction.enable, setLoadingRules, - toasts, search: isAllSelected ? { query: filterQuery } : { ids: ruleIds }, }); - updateRulesCache(res?.attributes?.results?.updated ?? []); }; const handleDisableActions = async () => { @@ -141,32 +117,24 @@ export const useBulkActions = ({ const enabledIds = selectedRules.filter(({ enabled }) => enabled).map(({ id }) => id); - const res = await executeRulesBulkAction({ + await executeBulkAction({ visibleRuleIds: enabledIds, action: BulkAction.disable, setLoadingRules, - toasts, search: isAllSelected ? { query: filterQuery } : { ids: enabledIds }, }); - updateRulesCache(res?.attributes?.results?.updated ?? []); }; const handleDuplicateAction = async () => { startTransaction({ name: BULK_RULE_ACTIONS.DUPLICATE }); closePopover(); - await executeRulesBulkAction({ + await executeBulkAction({ visibleRuleIds: selectedRuleIds, action: BulkAction.duplicate, setLoadingRules, - toasts, search: isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }, }); - invalidateRules(); - // We use prePackagedRulesStatus to display Prebuilt/Custom rules - // counters, so we need to invalidate it when the total number of rules - // changes. - invalidatePrePackagedRulesStatus(); clearRulesSelection(); }; @@ -182,29 +150,21 @@ export const useBulkActions = ({ startTransaction({ name: BULK_RULE_ACTIONS.DELETE }); - await executeRulesBulkAction({ + await executeBulkAction({ visibleRuleIds: selectedRuleIds, action: BulkAction.delete, setLoadingRules, - toasts, search: isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }, }); - invalidateRules(); - // We use prePackagedRulesStatus to display Prebuilt/Custom rules - // counters, so we need to invalidate it when the total number of rules - // changes. - invalidatePrePackagedRulesStatus(); }; const handleExportAction = async () => { closePopover(); startTransaction({ name: BULK_RULE_ACTIONS.EXPORT }); - const response = await executeRulesBulkAction({ + const response = await bulkExport({ visibleRuleIds: selectedRuleIds, - action: BulkAction.export, setLoadingRules, - toasts, search: isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }, }); @@ -232,7 +192,6 @@ export const useBulkActions = ({ let longTimeWarningToast: Toast; let isBulkEditFinished = false; - // disabling auto-refresh so user's selected rules won't disappear after table refresh closePopover(); const dryRunResult = await executeBulkActionsDryRun({ @@ -296,11 +255,10 @@ export const useBulkActions = ({ ); }, 5 * 1000); - const res = await executeRulesBulkAction({ + await executeBulkAction({ visibleRuleIds: selectedRuleIds, action: BulkAction.edit, setLoadingRules, - toasts, payload: { edit: [editPayload] }, onFinish: () => hideWarningToast(), search: prepareSearchParams({ @@ -310,10 +268,6 @@ export const useBulkActions = ({ }); isBulkEditFinished = true; - updateRulesCache(res?.attributes?.results?.updated ?? []); - if (getIsMounted()) { - await resolveTagsRefetch(bulkEditActionType); - } }; const isDeleteDisabled = containsLoading || selectedRuleIds.length === 0; @@ -332,7 +286,9 @@ export const useBulkActions = ({ disabled: missingActionPrivileges || containsLoading || (!containsDisabled && !isAllSelected), onClick: handleEnableAction, - toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, + toolTipContent: missingActionPrivileges + ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES + : undefined, toolTipPosition: 'right', icon: undefined, }, @@ -342,7 +298,9 @@ export const useBulkActions = ({ 'data-test-subj': 'duplicateRuleBulk', disabled: isEditDisabled, onClick: handleDuplicateAction, - toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, + toolTipContent: missingActionPrivileges + ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES + : undefined, toolTipPosition: 'right', icon: undefined, }, @@ -366,7 +324,9 @@ export const useBulkActions = ({ 'data-test-subj': 'addRuleActionsBulk', disabled: !hasActionsPrivileges || isEditDisabled, onClick: handleBulkEdit(BulkActionEditType.add_rule_actions), - toolTipContent: !hasActionsPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, + toolTipContent: !hasActionsPrivileges + ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES + : undefined, toolTipPosition: 'right', icon: undefined, }, @@ -376,7 +336,9 @@ export const useBulkActions = ({ 'data-test-subj': 'setScheduleBulk', disabled: isEditDisabled, onClick: handleBulkEdit(BulkActionEditType.set_schedule), - toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, + toolTipContent: missingActionPrivileges + ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES + : undefined, toolTipPosition: 'right', icon: undefined, }, @@ -386,7 +348,9 @@ export const useBulkActions = ({ 'data-test-subj': 'applyTimelineTemplateBulk', disabled: isEditDisabled, onClick: handleBulkEdit(BulkActionEditType.set_timeline), - toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, + toolTipContent: missingActionPrivileges + ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES + : undefined, toolTipPosition: 'right', icon: undefined, }, @@ -405,7 +369,9 @@ export const useBulkActions = ({ disabled: missingActionPrivileges || containsLoading || (!containsEnabled && !isAllSelected), onClick: handleDisableActions, - toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, + toolTipContent: missingActionPrivileges + ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES + : undefined, toolTipPosition: 'right', icon: undefined, }, @@ -439,7 +405,9 @@ export const useBulkActions = ({ 'data-test-subj': 'addTagsBulkEditRule', onClick: handleBulkEdit(BulkActionEditType.add_tags), disabled: isEditDisabled, - toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, + toolTipContent: missingActionPrivileges + ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES + : undefined, toolTipPosition: 'right', }, { @@ -448,7 +416,9 @@ export const useBulkActions = ({ 'data-test-subj': 'deleteTagsBulkEditRule', onClick: handleBulkEdit(BulkActionEditType.delete_tags), disabled: isEditDisabled, - toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, + toolTipContent: missingActionPrivileges + ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES + : undefined, toolTipPosition: 'right', }, ], @@ -463,7 +433,9 @@ export const useBulkActions = ({ 'data-test-subj': 'addIndexPatternsBulkEditRule', onClick: handleBulkEdit(BulkActionEditType.add_index_patterns), disabled: isEditDisabled, - toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, + toolTipContent: missingActionPrivileges + ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES + : undefined, toolTipPosition: 'right', }, { @@ -472,7 +444,9 @@ export const useBulkActions = ({ 'data-test-subj': 'deleteIndexPatternsBulkEditRule', onClick: handleBulkEdit(BulkActionEditType.delete_index_patterns), disabled: isEditDisabled, - toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, + toolTipContent: missingActionPrivileges + ? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES + : undefined, toolTipPosition: 'right', }, ], @@ -487,20 +461,17 @@ export const useBulkActions = ({ loadingRuleIds, startTransaction, hasMlPermissions, + executeBulkAction, setLoadingRules, - toasts, filterQuery, - updateRulesCache, - invalidateRules, - invalidatePrePackagedRulesStatus, + toasts, clearRulesSelection, confirmDeletion, + bulkExport, showBulkActionConfirmation, executeBulkActionsDryRun, filterOptions, completeBulkEditForm, - getIsMounted, - resolveTagsRefetch, ] ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions_confirmation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_confirmation.ts similarity index 89% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions_confirmation.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_confirmation.ts index d840abae1f2e2..9ce813fc6d9a2 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions_confirmation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_confirmation.ts @@ -7,13 +7,14 @@ import { useState, useCallback } from 'react'; import { useAsyncConfirmation } from '../rules_table/use_async_confirmation'; -import { useBoolState } from '../../../../../../common/hooks/use_bool_state'; +import { useBoolState } from '../../../../../common/hooks/use_bool_state'; import type { DryRunResult, BulkActionForConfirmation } from './types'; /** * hook that controls bulk actions confirmation modal window and its content */ +// TODO Why does this hook exist? Consider removing it altogether export const useBulkActionsConfirmation = () => { const [bulkAction, setBulkAction] = useState<BulkActionForConfirmation>(); const [dryRunResult, setDryRunResult] = useState<DryRunResult>(); @@ -31,6 +32,7 @@ export const useBulkActionsConfirmation = () => { // show bulk action confirmation window only if there is at least one failed rule, otherwise return early const hasFailedRules = (result?.failedRulesCount ?? 0) > 0; + // TODO Why is this logic here? Extract it out of this hook. if (!hasFailedRules) { return true; } diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions_dry_run.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_dry_run.ts similarity index 88% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions_dry_run.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_dry_run.ts index 5e14c0a57bf51..d11a2d6c167b8 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions_dry_run.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions_dry_run.ts @@ -11,9 +11,9 @@ import { useMutation } from '@tanstack/react-query'; import type { BulkAction, BulkActionEditType, -} from '../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import type { BulkActionResponse } from '../../../../../containers/detection_engine/rules'; -import { performBulkAction } from '../../../../../containers/detection_engine/rules'; +} from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import type { BulkActionResponse } from '../../../../rule_management/logic'; +import { performBulkAction } from '../../../../rule_management/logic'; import { computeDryRunPayload } from './utils/compute_dry_run_payload'; import { processDryRunResult } from './utils/dry_run_result'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_edit_form_flyout.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_edit_form_flyout.ts similarity index 89% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_edit_form_flyout.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_edit_form_flyout.ts index 6d629ae1869b4..063d10a737810 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_edit_form_flyout.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_edit_form_flyout.ts @@ -10,8 +10,8 @@ import { useAsyncConfirmation } from '../rules_table/use_async_confirmation'; import type { BulkActionEditPayload, BulkActionEditType, -} from '../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { useBoolState } from '../../../../../../common/hooks/use_bool_state'; +} from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { useBoolState } from '../../../../../common/hooks/use_bool_state'; export const useBulkEditFormFlyout = () => { const dataFormRef = useRef<BulkActionEditPayload | null>(null); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/compute_dry_run_payload.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.test.ts similarity index 93% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/compute_dry_run_payload.test.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.test.ts index 361f7edc4823f..b3fe47dd214a0 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/compute_dry_run_payload.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.test.ts @@ -8,7 +8,7 @@ import { BulkAction, BulkActionEditType, -} from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +} from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { computeDryRunPayload } from './compute_dry_run_payload'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/compute_dry_run_payload.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.ts similarity index 87% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/compute_dry_run_payload.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.ts index d8e6f78e33397..36ebb0d5a644d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/compute_dry_run_payload.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_payload.ts @@ -5,12 +5,12 @@ * 2.0. */ -import type { BulkActionEditPayload } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import type { BulkActionEditPayload } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { BulkAction, BulkActionEditType, -} from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { assertUnreachable } from '../../../../../../../../common/utility_types'; +} from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { assertUnreachable } from '../../../../../../../common/utility_types'; /** * helper utility that creates payload for _bulk_action API in dry mode diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/dry_run_result.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/dry_run_result.ts similarity index 85% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/dry_run_result.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/dry_run_result.ts index 5a15f03b9810d..627b7bf54cfd3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/dry_run_result.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/dry_run_result.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { BulkActionsDryRunErrCode } from '../../../../../../../../common/constants'; -import type { ExportRulesDetails } from '../../../../../../../../common/detection_engine/schemas/response/export_rules_details_schema'; -import type { BulkActionResponse } from '../../../../../../containers/detection_engine/rules'; +import { BulkActionsDryRunErrCode } from '../../../../../../../common/constants'; +import type { ExportRulesDetails } from '../../../../../../../common/detection_engine/rule_management'; +import type { BulkActionResponse } from '../../../../../rule_management/logic'; import type { DryRunResult } from '../types'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/prepare_search_params.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.test.ts similarity index 87% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/prepare_search_params.test.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.test.ts index ed7f18376c3f4..3c5dc13ffab66 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/prepare_search_params.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.test.ts @@ -6,14 +6,14 @@ */ import type { DryRunResult } from '../types'; -import type { FilterOptions } from '../../../../../../containers/detection_engine/rules/types'; +import type { FilterOptions } from '../../../../../rule_management/logic/types'; -import { convertRulesFilterToKQL } from '../../../../../../containers/detection_engine/rules/utils'; -import { BulkActionsDryRunErrCode } from '../../../../../../../../common/constants'; +import { convertRulesFilterToKQL } from '../../../../../rule_management/logic/utils'; +import { BulkActionsDryRunErrCode } from '../../../../../../../common/constants'; import { prepareSearchParams } from './prepare_search_params'; -jest.mock('../../../../../../containers/detection_engine/rules/utils', () => ({ +jest.mock('../../../../../rule_management/logic/utils', () => ({ convertRulesFilterToKQL: jest.fn().mockReturnValue('str'), })); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/prepare_search_params.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.ts similarity index 88% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/prepare_search_params.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.ts index 72a74a9fa500d..c761e56f4bfef 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/utils/prepare_search_params.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.ts @@ -6,10 +6,10 @@ */ import type { DryRunResult } from '../types'; -import type { FilterOptions } from '../../../../../../containers/detection_engine/rules/types'; +import type { FilterOptions } from '../../../../../rule_management/logic/types'; -import { convertRulesFilterToKQL } from '../../../../../../containers/detection_engine/rules/utils'; -import { BulkActionsDryRunErrCode } from '../../../../../../../../common/constants'; +import { convertRulesFilterToKQL } from '../../../../../rule_management/logic/utils'; +import { BulkActionsDryRunErrCode } from '../../../../../../../common/constants'; type PrepareSearchFilterProps = | { selectedRuleIds: string[]; dryRunResult?: DryRunResult } diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/README.md b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/feature_tour/README.md similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/README.md rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/feature_tour/README.md diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/rules_feature_tour.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/feature_tour/rules_feature_tour.tsx similarity index 98% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/rules_feature_tour.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/feature_tour/rules_feature_tour.tsx index 9558aab5239bb..d2f64ec882840 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/rules_feature_tour.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/feature_tour/rules_feature_tour.tsx @@ -23,8 +23,8 @@ import { import { noop } from 'lodash'; import type { FC } from 'react'; import React, { useEffect, useMemo, useState } from 'react'; -import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '../../../../../../../common/constants'; -import { useKibana } from '../../../../../../common/lib/kibana'; +import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '../../../../../../common/constants'; +import { useKibana } from '../../../../../common/lib/kibana'; import * as i18n from './translations'; export interface RulesFeatureTourContextType { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/feature_tour/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/feature_tour/translations.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/feature_tour/translations.ts diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.test.ts similarity index 60% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/helpers.test.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.test.ts index aa65c05db762c..babedf3c14905 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.test.ts @@ -6,52 +6,10 @@ */ import { Query } from '@elastic/eui'; -import { EXCEPTIONS_SEARCH_SCHEMA } from './exceptions/exceptions_search_bar'; -import { caseInsensitiveSort, getSearchFilters, showRulesTable } from './helpers'; +import { EXCEPTIONS_SEARCH_SCHEMA } from '../../../rule_exceptions_ui/pages/exceptions/exceptions_search_bar'; +import { caseInsensitiveSort, getSearchFilters } from './helpers'; describe('AllRulesTable Helpers', () => { - describe('showRulesTable', () => { - test('returns false when rulesCustomInstalled and rulesInstalled are null', () => { - const result = showRulesTable({ - rulesCustomInstalled: undefined, - rulesInstalled: undefined, - }); - expect(result).toBeFalsy(); - }); - - test('returns false when rulesCustomInstalled and rulesInstalled are 0', () => { - const result = showRulesTable({ - rulesCustomInstalled: 0, - rulesInstalled: 0, - }); - expect(result).toBeFalsy(); - }); - - test('returns false when both rulesCustomInstalled and rulesInstalled checks return false', () => { - const result = showRulesTable({ - rulesCustomInstalled: 0, - rulesInstalled: undefined, - }); - expect(result).toBeFalsy(); - }); - - test('returns true if rulesCustomInstalled is not null or 0', () => { - const result = showRulesTable({ - rulesCustomInstalled: 5, - rulesInstalled: undefined, - }); - expect(result).toBeTruthy(); - }); - - test('returns true if rulesInstalled is not null or 0', () => { - const result = showRulesTable({ - rulesCustomInstalled: undefined, - rulesInstalled: 5, - }); - expect(result).toBeTruthy(); - }); - }); - describe('caseInsensitiveSort', () => { describe('when an array of differently cased tags is passed', () => { const unsortedTags = ['atest', 'Ctest', 'Btest', 'ctest', 'btest', 'Atest']; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.ts similarity index 82% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/helpers.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.ts index bc95640d2d292..13666f514b0be 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/helpers.ts @@ -6,18 +6,8 @@ */ import type { Query } from '@elastic/eui'; -import type { ExportRulesDetails } from '../../../../../../common/detection_engine/schemas/response/export_rules_details_schema'; -import type { BulkActionSummary } from '../../../../containers/detection_engine/rules'; - -export const showRulesTable = ({ - rulesCustomInstalled, - rulesInstalled, -}: { - rulesCustomInstalled?: number; - rulesInstalled?: number; -}) => - (rulesCustomInstalled != null && rulesCustomInstalled > 0) || - (rulesInstalled != null && rulesInstalled > 0); +import type { ExportRulesDetails } from '../../../../../common/detection_engine/rule_management'; +import type { BulkActionSummary } from '../../../rule_management/logic'; export const caseInsensitiveSort = (tags: string[]): string[] => { return tags.sort((a: string, b: string) => a.toLowerCase().localeCompare(b.toLowerCase())); // Case insensitive diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/index.tsx new file mode 100644 index 0000000000000..5e93508339980 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/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 { EuiSpacer } from '@elastic/eui'; +import React, { useState } from 'react'; +import { RulesTables } from './rules_tables'; +import { AllRulesTabs, RulesTableToolbar } from './rules_table_toolbar'; + +/** + * Table Component for displaying all Rules for a given cluster. Provides the ability to filter + * by name, sort by enabled, and perform the following actions: + * * Enable/Disable + * * Duplicate + * * Delete + * * Import/Export + */ +export const AllRules = React.memo(() => { + const [activeTab, setActiveTab] = useState(AllRulesTabs.rules); + + return ( + <> + <RulesTableToolbar activeTab={activeTab} onTabChange={setActiveTab} /> + <EuiSpacer /> + <RulesTables selectedTab={activeTab} /> + </> + ); +}); + +AllRules.displayName = 'AllRules'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/popover_tooltip.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/popover_tooltip.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/popover_tooltip.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/popover_tooltip.tsx index 564f73c379c63..2d11e2e9a8009 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/popover_tooltip.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/popover_tooltip.tsx @@ -7,7 +7,7 @@ import React, { useState } from 'react'; import { EuiPopover, EuiButtonIcon } from '@elastic/eui'; -import * as i18n from '../translations'; +import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; interface PopoverTooltipProps { columnName: string; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/__mocks__/rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/__mocks__/rules_table_context.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/__mocks__/rules_table_context.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/__mocks__/rules_table_context.tsx diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx similarity index 96% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/rules_table_context.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx index 91f7079011aa1..78c391d175747 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/rules_table_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx @@ -14,16 +14,16 @@ import React, { useState, useRef, } from 'react'; -import { DEFAULT_RULES_TABLE_REFRESH_SETTING } from '../../../../../../../common/constants'; -import { invariant } from '../../../../../../../common/utils/invariant'; -import { useKibana, useUiSetting$ } from '../../../../../../common/lib/kibana'; +import { DEFAULT_RULES_TABLE_REFRESH_SETTING } from '../../../../../../common/constants'; +import { invariant } from '../../../../../../common/utils/invariant'; +import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; import type { FilterOptions, PaginationOptions, Rule, SortingOptions, -} from '../../../../../containers/detection_engine/rules/types'; -import { useFindRules } from './use_find_rules'; +} from '../../../../rule_management/logic/types'; +import { useFindRulesInMemory } from './use_find_rules_in_memory'; import { getRulesComparator } from './utils'; export interface RulesTableState { @@ -121,7 +121,7 @@ export interface LoadingRules { } export interface RulesTableActions { - reFetchRules: ReturnType<typeof useFindRules>['refetch']; + reFetchRules: ReturnType<typeof useFindRulesInMemory>['refetch']; setFilterOptions: (newFilter: Partial<FilterOptions>) => void; setIsAllSelected: React.Dispatch<React.SetStateAction<boolean>>; setIsInMemorySorting: (value: boolean) => void; @@ -239,7 +239,7 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide isFetching, isLoading, isRefetching, - } = useFindRules({ + } = useFindRulesInMemory({ isInMemorySorting, filterOptions, sortingOptions, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/use_async_confirmation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_async_confirmation.ts similarity index 97% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/use_async_confirmation.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_async_confirmation.ts index cce45f87d8ce3..3042906f87cf3 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/use_async_confirmation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_async_confirmation.ts @@ -18,6 +18,7 @@ interface UseAsyncConfirmationArgs { onFinish: () => void; } +// TODO move to common hooks export const useAsyncConfirmation = ({ onInit, onFinish, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/use_find_rules.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory.ts similarity index 79% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/use_find_rules.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory.ts index 2cdd3ab4b8e46..c1429fddbd312 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/use_find_rules.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/use_find_rules_in_memory.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { FindRulesQueryArgs } from '../../../../../containers/detection_engine/rules/use_find_rules_query'; -import { useFindRulesQuery } from '../../../../../containers/detection_engine/rules/use_find_rules_query'; +import type { FindRulesQueryArgs } from '../../../../rule_management/api/hooks/use_find_rules_query'; +import { useFindRules } from '../../../../rule_management/logic/use_find_rules'; interface UseFindRulesArgs extends FindRulesQueryArgs { isInMemorySorting: boolean; @@ -23,12 +23,11 @@ const MAX_RULES_PER_PAGE = 10000; * @param args - find rules arguments * @returns rules query result */ -export const useFindRules = (args: UseFindRulesArgs) => { +export const useFindRulesInMemory = (args: UseFindRulesArgs) => { const { pagination, filterOptions, sortingOptions, isInMemorySorting, refetchInterval } = args; // Use this query result when isInMemorySorting = true - const allRules = useFindRulesQuery( - ['all'], + const allRules = useFindRules( { pagination: { page: 1, perPage: MAX_RULES_PER_PAGE }, filterOptions }, { refetchInterval, @@ -38,8 +37,7 @@ export const useFindRules = (args: UseFindRulesArgs) => { ); // Use this query result when isInMemorySorting = false - const pagedRules = useFindRulesQuery( - ['paged'], + const pagedRules = useFindRules( { pagination, filterOptions, sortingOptions }, { refetchInterval, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/utils.ts similarity index 95% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/utils.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/utils.ts index 13fe9230d2114..5382a740f8213 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table/utils.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/utils.ts @@ -6,7 +6,7 @@ */ import { get } from 'lodash'; -import type { Rule, SortingOptions } from '../../../../../containers/detection_engine/rules/types'; +import type { Rule, SortingOptions } from '../../../../rule_management/logic/types'; /** * Returns a comparator function to be used with .sort() diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/rules_table_filters.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/rules_table_filters.tsx index 1f266fd62ff82..784d3dfc62427 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/rules_table_filters.tsx @@ -15,11 +15,13 @@ import { import { isEqual } from 'lodash/fp'; import React, { useCallback } from 'react'; import styled from 'styled-components'; -import { RULES_TABLE_ACTIONS } from '../../../../../../common/lib/apm/user_actions'; -import { useStartTransaction } from '../../../../../../common/lib/apm/use_start_transaction'; -import * as i18n from '../../translations'; +import { RULES_TABLE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; +import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; +import { usePrePackagedRulesStatus } from '../../../../rule_management/logic/use_pre_packaged_rules_status'; +import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; import { useRulesTableContext } from '../rules_table/rules_table_context'; import { TagsFilterPopover } from './tags_filter_popover'; +import { useTags } from '../../../../rule_management/logic/use_tags'; const FilterWrapper = styled(EuiFlexGroup)` margin-bottom: ${({ theme }) => theme.eui.euiSizeXS}; @@ -34,26 +36,20 @@ const SearchBarWrapper = styled(EuiFlexItem)` } `; -interface RulesTableFiltersProps { - rulesCustomInstalled?: number; - rulesInstalled?: number; - allTags: string[]; -} - /** * Collection of filters for filtering data within the RulesTable. Contains search bar, Elastic/Custom * Rules filter button toggle, and tag selection */ -const RulesTableFiltersComponent = ({ - rulesCustomInstalled, - rulesInstalled, - allTags, -}: RulesTableFiltersProps) => { +const RulesTableFiltersComponent = () => { const { startTransaction } = useStartTransaction(); const { state: { filterOptions }, actions: { setFilterOptions }, } = useRulesTableContext(); + const { data: allTags = [] } = useTags(); + const { data: prePackagedRulesStatus } = usePrePackagedRulesStatus(); + const rulesCustomInstalled = prePackagedRulesStatus?.rules_custom_installed; + const rulesInstalled = prePackagedRulesStatus?.rules_installed; const { showCustomRules, showElasticRules, tags: selectedTags } = filterOptions; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/tags_filter_popover.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/tags_filter_popover.test.tsx diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/tags_filter_popover.tsx similarity index 94% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/tags_filter_popover.tsx index 683e36c391614..b74cf38cc9266 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_filters/tags_filter_popover.tsx @@ -19,8 +19,8 @@ import { EuiPopoverTitle, } from '@elastic/eui'; import styled from 'styled-components'; -import * as i18n from '../../translations'; -import { toggleSelectedGroup } from '../../../../../../common/components/ml_popover/jobs_table/filters/toggle_selected_group'; +import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations'; +import { toggleSelectedGroup } from '../../../../../common/components/ml_popover/jobs_table/filters/toggle_selected_group'; import { caseInsensitiveSort } from '../helpers'; interface TagsFilterPopoverProps { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_toolbar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar.tsx similarity index 91% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_toolbar.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar.tsx index cf8d253dbcc89..7d9fda7203702 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_toolbar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar.tsx @@ -10,9 +10,9 @@ import { EuiSwitch, EuiTab, EuiTabs, EuiToolTip } from '@elastic/eui'; import React, { useCallback } from 'react'; import styled from 'styled-components'; import { useRulesTableContext } from './rules_table/rules_table_context'; -import * as i18n from '../translations'; -import { RULES_TABLE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; -import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; +import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; +import { RULES_TABLE_ACTIONS } from '../../../../common/lib/apm/user_actions'; +import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; const ToolbarLayout = styled.div` display: grid; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_utility_bar.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_utility_bar.test.tsx similarity index 67% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_utility_bar.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_utility_bar.test.tsx index 4fecfc4afa6a2..2b88020f4ff35 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_utility_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_utility_bar.test.tsx @@ -10,26 +10,28 @@ import { mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; import { getShowingRulesParams, RulesTableUtilityBar } from './rules_table_utility_bar'; -import { TestProviders } from '../../../../../common/mock'; +import { TestProviders } from '../../../../common/mock'; +import { useRulesTableContextMock } from './rules_table/__mocks__/rules_table_context'; +import { useRulesTableContext } from './rules_table/rules_table_context'; jest.mock('./rules_table/rules_table_context'); describe('RulesTableUtilityBar', () => { it('renders RulesTableUtilityBar total rules and selected rules', () => { + const rulesTableContext = useRulesTableContextMock.create(); + rulesTableContext.state.pagination = { + page: 1, + perPage: 10, + total: 21, + }; + rulesTableContext.state.selectedRuleIds = ['testId']; + (useRulesTableContext as jest.Mock).mockReturnValue(rulesTableContext); + const wrapper = mount( <TestProviders> <RulesTableUtilityBar canBulkEdit - onRefresh={jest.fn()} - pagination={{ - page: 1, - perPage: 10, - total: 21, - }} - numberSelectedItems={1} onGetBulkItemsPopoverContent={jest.fn()} - isAutoRefreshOn={true} - onRefreshSwitch={jest.fn()} onToggleSelectAll={jest.fn()} /> </TestProviders> @@ -43,45 +45,12 @@ describe('RulesTableUtilityBar', () => { ); }); - it('renders correct pagination label according to pagination data', () => { - const wrapper = mount( - <TestProviders> - <RulesTableUtilityBar - canBulkEdit - onRefresh={jest.fn()} - pagination={{ - page: 1, - perPage: 10, - total: 21, - }} - numberSelectedItems={1} - onGetBulkItemsPopoverContent={jest.fn()} - isAutoRefreshOn={true} - onRefreshSwitch={jest.fn()} - onToggleSelectAll={jest.fn()} - /> - </TestProviders> - ); - expect(wrapper.find('[data-test-subj="showingRules"]').at(0).text()).toEqual( - 'Showing 1-10 of 21 rules' - ); - }); - it('renders utility actions if user has permissions', () => { const wrapper = mount( <TestProviders> <RulesTableUtilityBar canBulkEdit - onRefresh={jest.fn()} - pagination={{ - page: 1, - perPage: 10, - total: 21, - }} - numberSelectedItems={1} onGetBulkItemsPopoverContent={jest.fn()} - isAutoRefreshOn={true} - onRefreshSwitch={jest.fn()} onToggleSelectAll={jest.fn()} /> </TestProviders> @@ -95,16 +64,7 @@ describe('RulesTableUtilityBar', () => { <TestProviders> <RulesTableUtilityBar canBulkEdit={false} - onRefresh={jest.fn()} - pagination={{ - page: 1, - perPage: 10, - total: 21, - }} - numberSelectedItems={1} onGetBulkItemsPopoverContent={jest.fn()} - isAutoRefreshOn={true} - onRefreshSwitch={jest.fn()} onToggleSelectAll={jest.fn()} /> </TestProviders> @@ -113,22 +73,15 @@ describe('RulesTableUtilityBar', () => { expect(wrapper.find('[data-test-subj="bulkActions"]').exists()).toBeFalsy(); }); - it('invokes refresh on refresh action click', () => { - const mockRefresh = jest.fn(); + it('invokes rules refetch on refresh action click', () => { + const rulesTableContext = useRulesTableContextMock.create(); + (useRulesTableContext as jest.Mock).mockReturnValue(rulesTableContext); + const wrapper = mount( <TestProviders> <RulesTableUtilityBar canBulkEdit - onRefresh={mockRefresh} - pagination={{ - page: 1, - perPage: 10, - total: 21, - }} - numberSelectedItems={1} onGetBulkItemsPopoverContent={jest.fn()} - isAutoRefreshOn={true} - onRefreshSwitch={jest.fn()} onToggleSelectAll={jest.fn()} /> </TestProviders> @@ -136,25 +89,19 @@ describe('RulesTableUtilityBar', () => { wrapper.find('[data-test-subj="refreshRulesAction"] button').at(0).simulate('click'); - expect(mockRefresh).toHaveBeenCalled(); + expect(rulesTableContext.actions.reFetchRules).toHaveBeenCalledTimes(1); }); - it('invokes onRefreshSwitch when auto refresh switch is clicked if there are not selected items', async () => { - const mockSwitch = jest.fn(); + it('invokes rule refetch when auto refresh switch is clicked if there are not selected items', async () => { + const rulesTableContext = useRulesTableContextMock.create(); + rulesTableContext.state.isRefreshOn = false; + (useRulesTableContext as jest.Mock).mockReturnValue(rulesTableContext); + const wrapper = mount( <TestProviders> <RulesTableUtilityBar canBulkEdit - onRefresh={jest.fn()} - pagination={{ - page: 1, - perPage: 10, - total: 21, - }} - numberSelectedItems={0} onGetBulkItemsPopoverContent={jest.fn()} - isAutoRefreshOn={true} - onRefreshSwitch={mockSwitch} onToggleSelectAll={jest.fn()} /> </TestProviders> @@ -163,26 +110,21 @@ describe('RulesTableUtilityBar', () => { await waitFor(() => { wrapper.find('[data-test-subj="refreshSettings"] button').first().simulate('click'); wrapper.find('[data-test-subj="refreshSettingsSwitch"] button').first().simulate('click'); - expect(mockSwitch).toHaveBeenCalledTimes(1); + expect(rulesTableContext.actions.reFetchRules).toHaveBeenCalledTimes(1); }); }); it('does not invokes onRefreshSwitch when auto refresh switch is clicked if there are selected items', async () => { - const mockSwitch = jest.fn(); + const rulesTableContext = useRulesTableContextMock.create(); + rulesTableContext.state.isRefreshOn = false; + rulesTableContext.state.selectedRuleIds = ['testId']; + (useRulesTableContext as jest.Mock).mockReturnValue(rulesTableContext); + const wrapper = mount( <TestProviders> <RulesTableUtilityBar canBulkEdit - onRefresh={jest.fn()} - pagination={{ - page: 1, - perPage: 10, - total: 21, - }} - numberSelectedItems={1} onGetBulkItemsPopoverContent={jest.fn()} - isAutoRefreshOn={true} - onRefreshSwitch={mockSwitch} onToggleSelectAll={jest.fn()} /> </TestProviders> @@ -191,7 +133,7 @@ describe('RulesTableUtilityBar', () => { await waitFor(() => { wrapper.find('[data-test-subj="refreshSettings"] button').first().simulate('click'); wrapper.find('[data-test-subj="refreshSettingsSwitch"] button').first().simulate('click'); - expect(mockSwitch).not.toHaveBeenCalled(); + expect(rulesTableContext.actions.reFetchRules).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_utility_bar.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_utility_bar.tsx similarity index 78% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_utility_bar.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_utility_bar.tsx index 7f9b0dba04881..7c91e27423697 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_utility_bar.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table_utility_bar.tsx @@ -22,11 +22,13 @@ import { UtilityBarGroup, UtilityBarSection, UtilityBarText, -} from '../../../../../common/components/utility_bar'; -import * as i18n from '../translations'; -import { useKibana } from '../../../../../common/lib/kibana'; +} from '../../../../common/components/utility_bar'; +import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; +import { useKibana } from '../../../../common/lib/kibana'; import { useRulesTableContext } from './rules_table/rules_table_context'; -import type { PaginationOptions } from '../../../../containers/detection_engine/rules/types'; +import type { PaginationOptions } from '../../../rule_management/logic/types'; +import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; +import { RULES_TABLE_ACTIONS } from '../../../../common/lib/apm/user_actions'; export const getShowingRulesParams = ({ page, perPage, total: totalRules }: PaginationOptions) => { const firstInPage = totalRules === 0 ? 0 : (page - 1) * perPage + 1; @@ -37,35 +39,27 @@ export const getShowingRulesParams = ({ page, perPage, total: totalRules }: Pagi interface RulesTableUtilityBarProps { canBulkEdit: boolean; - isAllSelected?: boolean; - isAutoRefreshOn?: boolean; - numberSelectedItems: number; onGetBulkItemsPopoverContent?: (closePopover: () => void) => EuiContextMenuPanelDescriptor[]; - onRefresh: () => void; - onRefreshSwitch: (checked: boolean) => void; onToggleSelectAll: () => void; - pagination: PaginationOptions; isBulkActionInProgress?: boolean; - hasDisabledActions?: boolean; } export const RulesTableUtilityBar = React.memo<RulesTableUtilityBarProps>( - ({ - canBulkEdit, - isAllSelected, - isAutoRefreshOn, - numberSelectedItems, - onGetBulkItemsPopoverContent, - onRefresh, - onRefreshSwitch, - onToggleSelectAll, - pagination, - isBulkActionInProgress, - hasDisabledActions, - }) => { + ({ canBulkEdit, onGetBulkItemsPopoverContent, onToggleSelectAll, isBulkActionInProgress }) => { const { timelines } = useKibana().services; + const { startTransaction } = useStartTransaction(); const rulesTableContext = useRulesTableContext(); - const isAnyRuleSelected = numberSelectedItems > 0; + const { pagination, selectedRuleIds, isRefreshOn, isAllSelected, loadingRulesAction } = + rulesTableContext.state; + const { reFetchRules, setIsRefreshOn } = rulesTableContext.actions; + const selectedRulesCount = isAllSelected ? pagination.total : selectedRuleIds.length; + const isAnyRuleSelected = selectedRulesCount > 0; + const hasDisabledActions = loadingRulesAction != null; + + const handleRefreshRules = useCallback(() => { + startTransaction({ name: RULES_TABLE_ACTIONS.REFRESH }); + reFetchRules(); + }, [reFetchRules, startTransaction]); const handleGetBulkItemsPopoverContent = useCallback( (closePopover: () => void): JSX.Element | null => { @@ -85,12 +79,14 @@ export const RulesTableUtilityBar = React.memo<RulesTableUtilityBarProps>( const handleAutoRefreshSwitch = useCallback( (closePopover: () => void) => (e: EuiSwitchEvent) => { - if (onRefreshSwitch != null) { - onRefreshSwitch(e.target.checked); - closePopover(); + const refreshOn = e.target.checked; + if (refreshOn) { + reFetchRules(); } + setIsRefreshOn(refreshOn); + closePopover(); }, - [onRefreshSwitch] + [reFetchRules, setIsRefreshOn] ); const handleGetRefreshSettingsPopoverContent = useCallback( @@ -100,7 +96,7 @@ export const RulesTableUtilityBar = React.memo<RulesTableUtilityBarProps>( <EuiSwitch key="allRulesAutoRefreshSwitch" label={i18n.REFRESH_RULE_POPOVER_DESCRIPTION} - checked={isAutoRefreshOn ?? false} + checked={isRefreshOn ?? false} onChange={handleAutoRefreshSwitch(closePopover)} compressed disabled={isAnyRuleSelected} @@ -122,7 +118,7 @@ export const RulesTableUtilityBar = React.memo<RulesTableUtilityBarProps>( ]} /> ), - [isAutoRefreshOn, handleAutoRefreshSwitch, isAnyRuleSelected] + [isRefreshOn, handleAutoRefreshSwitch, isAnyRuleSelected] ); return ( @@ -136,7 +132,7 @@ export const RulesTableUtilityBar = React.memo<RulesTableUtilityBarProps>( <> <UtilityBarGroup data-test-subj="tableBulkActions"> <UtilityBarText dataTestSubj="selectedRules"> - {i18n.SELECTED_RULES(numberSelectedItems)} + {i18n.SELECTED_RULES(selectedRulesCount)} </UtilityBarText> {canBulkEdit && ( @@ -170,7 +166,7 @@ export const RulesTableUtilityBar = React.memo<RulesTableUtilityBarProps>( dataTestSubj="refreshRulesAction" iconSide="left" iconType="refresh" - onClick={onRefresh} + onClick={handleRefreshRules} > {i18n.REFRESH} </UtilityBarAction> diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx new file mode 100644 index 0000000000000..4379674c9738f --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_tables.tsx @@ -0,0 +1,290 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiBasicTable, + EuiConfirmModal, + EuiEmptyPrompt, + EuiLoadingContent, + EuiProgress, +} from '@elastic/eui'; +import React, { useCallback, useMemo, useRef } from 'react'; +import { RULES_TABLE_PAGE_SIZE_OPTIONS } from '../../../../../common/constants'; +import { Loader } from '../../../../common/components/loader'; +import { useBoolState } from '../../../../common/hooks/use_bool_state'; +import { useValueChanged } from '../../../../common/hooks/use_value_changed'; +import { PrePackagedRulesPrompt } from '../../../../detections/components/rules/pre_packaged_rules/load_empty_prompt'; +import type { Rule, RulesSortingFields } from '../../../rule_management/logic'; +import { usePrePackagedRulesStatus } from '../../../rule_management/logic/use_pre_packaged_rules_status'; +import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; +import type { EuiBasicTableOnChange } from '../../../../detections/pages/detection_engine/rules/types'; +import { BulkActionDryRunConfirmation } from './bulk_actions/bulk_action_dry_run_confirmation'; +import { BulkEditFlyout } from './bulk_actions/bulk_edit_flyout'; +import { useBulkActions } from './bulk_actions/use_bulk_actions'; +import { useBulkActionsConfirmation } from './bulk_actions/use_bulk_actions_confirmation'; +import { useBulkActionsDryRun } from './bulk_actions/use_bulk_actions_dry_run'; +import { useBulkEditFormFlyout } from './bulk_actions/use_bulk_edit_form_flyout'; +import { useRulesTableContext } from './rules_table/rules_table_context'; +import { useAsyncConfirmation } from './rules_table/use_async_confirmation'; +import { RulesTableFilters } from './rules_table_filters/rules_table_filters'; +import { AllRulesTabs } from './rules_table_toolbar'; +import { RulesTableUtilityBar } from './rules_table_utility_bar'; +import { useMonitoringColumns, useRulesColumns } from './use_columns'; +import { useUserData } from '../../../../detections/components/user_info'; +import { hasUserCRUDPermission } from '../../../../common/utils/privileges'; + +const INITIAL_SORT_FIELD = 'enabled'; + +interface RulesTableProps { + selectedTab: AllRulesTabs; +} + +const NO_ITEMS_MESSAGE = ( + <EuiEmptyPrompt title={<h3>{i18n.NO_RULES}</h3>} titleSize="xs" body={i18n.NO_RULES_BODY} /> +); + +/** + * Table Component for displaying all Rules for a given cluster. Provides the ability to filter + * by name, sort by enabled, and perform the following actions: + * * Enable/Disable + * * Duplicate + * * Delete + * * Import/Export + */ +// eslint-disable-next-line complexity +export const RulesTables = React.memo<RulesTableProps>(({ selectedTab }) => { + const [{ canUserCRUD }] = useUserData(); + const hasPermissions = hasUserCRUDPermission(canUserCRUD); + + const tableRef = useRef<EuiBasicTable>(null); + const rulesTableContext = useRulesTableContext(); + const { data: prePackagedRulesStatus, isLoading: isPrepackagedStatusLoading } = + usePrePackagedRulesStatus(); + + const { + state: { + rules, + filterOptions, + isActionInProgress, + isAllSelected, + isFetched, + isLoading, + isRefetching, + loadingRuleIds, + loadingRulesAction, + pagination, + selectedRuleIds, + sortingOptions, + }, + actions: { setIsAllSelected, setPage, setPerPage, setSelectedRuleIds, setSortingOptions }, + } = rulesTableContext; + + const [isDeleteConfirmationVisible, showDeleteConfirmation, hideDeleteConfirmation] = + useBoolState(); + + const [confirmDeletion, handleDeletionConfirm, handleDeletionCancel] = useAsyncConfirmation({ + onInit: showDeleteConfirmation, + onFinish: hideDeleteConfirmation, + }); + + const { + bulkActionsDryRunResult, + bulkAction, + isBulkActionConfirmationVisible, + showBulkActionConfirmation, + cancelBulkActionConfirmation, + approveBulkActionConfirmation, + } = useBulkActionsConfirmation(); + + const { + bulkEditActionType, + isBulkEditFlyoutVisible, + handleBulkEditFormConfirm, + handleBulkEditFormCancel, + completeBulkEditForm, + } = useBulkEditFormFlyout(); + + const { isBulkActionsDryRunLoading, executeBulkActionsDryRun } = useBulkActionsDryRun(); + + const getBulkItemsPopoverContent = useBulkActions({ + filterOptions, + confirmDeletion, + showBulkActionConfirmation, + completeBulkEditForm, + executeBulkActionsDryRun, + }); + + const paginationMemo = useMemo( + () => ({ + pageIndex: pagination.page - 1, + pageSize: pagination.perPage, + totalItemCount: pagination.total, + pageSizeOptions: RULES_TABLE_PAGE_SIZE_OPTIONS, + }), + [pagination] + ); + + const tableOnChangeCallback = useCallback( + ({ page, sort }: EuiBasicTableOnChange) => { + setSortingOptions({ + field: (sort?.field as RulesSortingFields) ?? INITIAL_SORT_FIELD, // Narrowing EuiBasicTable sorting types + order: sort?.direction ?? 'desc', + }); + setPage(page.index + 1); + setPerPage(page.size); + }, + [setPage, setPerPage, setSortingOptions] + ); + + const rulesColumns = useRulesColumns({ hasCRUDPermissions: hasPermissions }); + const monitoringColumns = useMonitoringColumns({ hasCRUDPermissions: hasPermissions }); + + const isSelectAllCalled = useRef(false); + + // TODO Remove this synchronization logic after https://github.com/elastic/eui/issues/6184 is implemented + // Synchronize selectedRuleIds with EuiBasicTable's selected rows + useValueChanged((ruleIds) => { + if (tableRef.current != null) { + tableRef.current.setSelection(rules.filter((rule) => ruleIds.includes(rule.id))); + } + }, selectedRuleIds); + + const euiBasicTableSelectionProps = useMemo( + () => ({ + selectable: (item: Rule) => !loadingRuleIds.includes(item.id), + onSelectionChange: (selected: Rule[]) => { + /** + * EuiBasicTable doesn't provide declarative API to control selected rows. + * This limitation requires us to synchronize selection state manually using setSelection(). + * But it creates a chain reaction when the user clicks Select All: + * selectAll() -> setSelection() -> onSelectionChange() -> setSelection(). + * To break the chain we should check whether the onSelectionChange was triggered + * by the Select All action or not. + * + */ + if (isSelectAllCalled.current) { + isSelectAllCalled.current = false; + // Handle special case of unselecting all rules via checkbox + // after all rules were selected via Bulk select. + if (selected.length === 0) { + setIsAllSelected(false); + setSelectedRuleIds([]); + } + } else { + setSelectedRuleIds(selected.map(({ id }) => id)); + setIsAllSelected(false); + } + }, + }), + [loadingRuleIds, setIsAllSelected, setSelectedRuleIds] + ); + + const toggleSelectAll = useCallback(() => { + isSelectAllCalled.current = true; + setIsAllSelected(!isAllSelected); + setSelectedRuleIds(!isAllSelected ? rules.map(({ id }) => id) : []); + }, [rules, isAllSelected, setIsAllSelected, setSelectedRuleIds]); + + const isTableEmpty = + !isPrepackagedStatusLoading && + prePackagedRulesStatus?.rules_custom_installed === 0 && + prePackagedRulesStatus.rules_installed === 0; + + const shouldShowRulesTable = !isPrepackagedStatusLoading && !isLoading && !isTableEmpty; + + const tableProps = + selectedTab === AllRulesTabs.rules + ? { + 'data-test-subj': 'rules-table', + columns: rulesColumns, + } + : { 'data-test-subj': 'monitoring-table', columns: monitoringColumns }; + + const shouldShowLinearProgress = isFetched && isRefetching; + const shouldShowLoadingOverlay = (!isFetched && isRefetching) || isActionInProgress; + + return ( + <> + {shouldShowLinearProgress && ( + <EuiProgress + data-test-subj="loadingRulesInfoProgress" + size="xs" + position="absolute" + color="accent" + /> + )} + {shouldShowLoadingOverlay && ( + <Loader data-test-subj="loadingPanelAllRulesTable" overlay size="xl" /> + )} + {shouldShowRulesTable && <RulesTableFilters />} + {isTableEmpty && <PrePackagedRulesPrompt />} + {isLoading && ( + <EuiLoadingContent data-test-subj="initialLoadingPanelAllRulesTable" lines={10} /> + )} + {isDeleteConfirmationVisible && ( + <EuiConfirmModal + title={i18n.DELETE_CONFIRMATION_TITLE} + onCancel={handleDeletionCancel} + onConfirm={handleDeletionConfirm} + confirmButtonText={i18n.DELETE_CONFIRMATION_CONFIRM} + cancelButtonText={i18n.DELETE_CONFIRMATION_CANCEL} + buttonColor="danger" + defaultFocusedButton="confirm" + data-test-subj="allRulesDeleteConfirmationModal" + > + <p>{i18n.DELETE_CONFIRMATION_BODY}</p> + </EuiConfirmModal> + )} + {isBulkActionConfirmationVisible && bulkAction && ( + <BulkActionDryRunConfirmation + bulkAction={bulkAction} + result={bulkActionsDryRunResult} + onCancel={cancelBulkActionConfirmation} + onConfirm={approveBulkActionConfirmation} + /> + )} + {isBulkEditFlyoutVisible && bulkEditActionType !== undefined && ( + <BulkEditFlyout + rulesCount={bulkActionsDryRunResult?.succeededRulesCount ?? 0} + editAction={bulkEditActionType} + onClose={handleBulkEditFormCancel} + onConfirm={handleBulkEditFormConfirm} + /> + )} + {shouldShowRulesTable && ( + <> + <RulesTableUtilityBar + canBulkEdit={hasPermissions} + onGetBulkItemsPopoverContent={getBulkItemsPopoverContent} + onToggleSelectAll={toggleSelectAll} + isBulkActionInProgress={isBulkActionsDryRunLoading || loadingRulesAction != null} + /> + <EuiBasicTable + itemId="id" + items={rules} + isSelectable={hasPermissions} + noItemsMessage={NO_ITEMS_MESSAGE} + onChange={tableOnChangeCallback} + pagination={paginationMemo} + ref={tableRef} + selection={hasPermissions ? euiBasicTableSelectionProps : undefined} + sorting={{ + sort: { + // EuiBasicTable has incorrect `sort.field` types which accept only `keyof Item` and reject fields in dot notation + field: sortingOptions.field as keyof Rule, + direction: sortingOptions.order, + }, + }} + {...tableProps} + /> + </> + )} + </> + ); +}); + +RulesTables.displayName = 'RulesTables'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/table_header_tooltip_cell.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/table_header_tooltip_cell.test.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/table_header_tooltip_cell.test.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/table_header_tooltip_cell.test.tsx diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/table_header_tooltip_cell.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/table_header_tooltip_cell.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/table_header_tooltip_cell.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/table_header_tooltip_cell.tsx diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx similarity index 76% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx index c5032a1eb9b72..d0e6c0198ad70 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_columns.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx @@ -8,50 +8,49 @@ import type { EuiBasicTableColumn, EuiTableActionsColumnType } from '@elastic/eui'; import { EuiBadge, EuiLink, EuiText, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useMemo } from 'react'; import moment from 'moment'; -import { IntegrationsPopover } from '../../../../components/rules/related_integrations/integrations_popover'; +import React, { useMemo } from 'react'; import { DEFAULT_RELATIVE_DATE_THRESHOLD, SecurityPageName, SHOW_RELATED_INTEGRATIONS_SETTING, -} from '../../../../../../common/constants'; -import { isMlRule } from '../../../../../../common/machine_learning/helpers'; -import { getEmptyTagValue } from '../../../../../common/components/empty_value'; -import { FormattedRelativePreferenceDate } from '../../../../../common/components/formatted_date'; -import { getRuleDetailsTabUrl } from '../../../../../common/components/link_to/redirect_to_detection_engine'; -import { PopoverItems } from '../../../../../common/components/popover_items'; -import { useKibana, useUiSetting$ } from '../../../../../common/lib/kibana'; -import { canEditRuleWithActions, getToolTipContent } from '../../../../../common/utils/privileges'; -import { RuleSwitch } from '../../../../components/rules/rule_switch'; -import { SeverityBadge } from '../../../../components/rules/severity_badge'; -import type { Rule } from '../../../../containers/detection_engine/rules'; -import { useRulesTableContext } from './rules_table/rules_table_context'; -import * as i18n from '../translations'; +} from '../../../../../common/constants'; +import type { + DurationMetric, + RuleExecutionSummary, +} from '../../../../../common/detection_engine/rule_monitoring'; +import { isMlRule } from '../../../../../common/machine_learning/helpers'; +import { getEmptyTagValue } from '../../../../common/components/empty_value'; +import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; +import { SecuritySolutionLinkAnchor } from '../../../../common/components/links'; +import { getRuleDetailsTabUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; +import { PopoverItems } from '../../../../common/components/popover_items'; +import { useKibana, useUiSetting$ } from '../../../../common/lib/kibana'; +import { + canEditRuleWithActions, + explainLackOfPermission, +} from '../../../../common/utils/privileges'; +import { IntegrationsPopover } from '../../../../detections/components/rules/related_integrations/integrations_popover'; +import { RuleStatusBadge } from '../../../../detections/components/rules/rule_execution_status'; +import { RuleSwitch } from '../../../../detections/components/rules/rule_switch'; +import { SeverityBadge } from '../../../../detections/components/rules/severity_badge'; +import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; +import { RuleDetailTabs } from '../../../rule_details_ui/pages/rule_details'; +import type { Rule } from '../../../rule_management/logic'; import { PopoverTooltip } from './popover_tooltip'; +import { useRulesTableContext } from './rules_table/rules_table_context'; import { TableHeaderTooltipCell } from './table_header_tooltip_cell'; import { useHasActionsPrivileges } from './use_has_actions_privileges'; import { useHasMlPermissions } from './use_has_ml_permissions'; -import { getRulesTableActions } from './rules_table_actions'; -import { RuleStatusBadge } from '../../../../components/rules/rule_execution_status'; -import type { - DurationMetric, - RuleExecutionSummary, -} from '../../../../../../common/detection_engine/rule_monitoring'; -import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; -import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; -import { useInvalidateRules } from '../../../../containers/detection_engine/rules/use_find_rules_query'; -import { useInvalidatePrePackagedRulesStatus } from '../../../../containers/detection_engine/rules/use_pre_packaged_rules_status'; -import { SecuritySolutionLinkAnchor } from '../../../../../common/components/links'; -import { RuleDetailTabs } from '../details'; +import { useRulesTableActions } from './use_rules_table_actions'; export type TableColumn = EuiBasicTableColumn<Rule> | EuiTableActionsColumnType<Rule>; interface ColumnsProps { - hasPermissions: boolean; + hasCRUDPermissions: boolean; } -const useEnabledColumn = ({ hasPermissions }: ColumnsProps): TableColumn => { +const useEnabledColumn = ({ hasCRUDPermissions }: ColumnsProps): TableColumn => { const hasMlPermissions = useHasMlPermissions(); const hasActionsPrivileges = useHasActionsPrivileges(); const { loadingRulesAction, loadingRuleIds } = useRulesTableContext().state; @@ -68,15 +67,20 @@ const useEnabledColumn = ({ hasPermissions }: ColumnsProps): TableColumn => { render: (_, rule: Rule) => ( <EuiToolTip position="top" - content={getToolTipContent(rule, hasMlPermissions, hasActionsPrivileges)} + content={explainLackOfPermission( + rule, + hasMlPermissions, + hasActionsPrivileges, + hasCRUDPermissions + )} > <RuleSwitch id={rule.id} enabled={rule.enabled} isDisabled={ !canEditRuleWithActions(rule, hasActionsPrivileges) || - !hasPermissions || - (isMlRule(rule.type) && !hasMlPermissions && !rule.enabled) + !hasCRUDPermissions || + (isMlRule(rule.type) && !hasMlPermissions) } isLoading={loadingIds.includes(rule.id)} /> @@ -85,7 +89,7 @@ const useEnabledColumn = ({ hasPermissions }: ColumnsProps): TableColumn => { width: '95px', sortable: true, }), - [hasActionsPrivileges, hasMlPermissions, hasPermissions, loadingIds] + [hasActionsPrivileges, hasMlPermissions, hasCRUDPermissions, loadingIds] ); }; @@ -162,42 +166,14 @@ const INTEGRATIONS_COLUMN: TableColumn = { }; const useActionsColumn = (): EuiTableActionsColumnType<Rule> => { - const { navigateToApp } = useKibana().services.application; - const hasActionsPrivileges = useHasActionsPrivileges(); - const toasts = useAppToasts(); - const { setLoadingRules } = useRulesTableContext().actions; - const { startTransaction } = useStartTransaction(); - const invalidateRules = useInvalidateRules(); - const invalidatePrePackagedRulesStatus = useInvalidatePrePackagedRulesStatus(); + const actions = useRulesTableActions(); - return useMemo( - () => ({ - actions: getRulesTableActions({ - toasts, - navigateToApp, - invalidateRules, - invalidatePrePackagedRulesStatus, - actionsPrivileges: hasActionsPrivileges, - setLoadingRules, - startTransaction, - }), - width: '40px', - }), - [ - hasActionsPrivileges, - invalidatePrePackagedRulesStatus, - invalidateRules, - navigateToApp, - setLoadingRules, - startTransaction, - toasts, - ] - ); + return useMemo(() => ({ actions, width: '40px' }), [actions]); }; -export const useRulesColumns = ({ hasPermissions }: ColumnsProps): TableColumn[] => { +export const useRulesColumns = ({ hasCRUDPermissions }: ColumnsProps): TableColumn[] => { const actionsColumn = useActionsColumn(); - const enabledColumn = useEnabledColumn({ hasPermissions }); + const enabledColumn = useEnabledColumn({ hasCRUDPermissions }); const ruleNameColumn = useRuleNameColumn(); const { isInMemorySorting } = useRulesTableContext().state; const [showRelatedIntegrations] = useUiSetting$<boolean>(SHOW_RELATED_INTEGRATIONS_SETTING); @@ -292,12 +268,12 @@ export const useRulesColumns = ({ hasPermissions }: ColumnsProps): TableColumn[] width: '65px', }, enabledColumn, - ...(hasPermissions ? [actionsColumn] : []), + ...(hasCRUDPermissions ? [actionsColumn] : []), ], [ actionsColumn, enabledColumn, - hasPermissions, + hasCRUDPermissions, isInMemorySorting, ruleNameColumn, showRelatedIntegrations, @@ -305,10 +281,10 @@ export const useRulesColumns = ({ hasPermissions }: ColumnsProps): TableColumn[] ); }; -export const useMonitoringColumns = ({ hasPermissions }: ColumnsProps): TableColumn[] => { +export const useMonitoringColumns = ({ hasCRUDPermissions }: ColumnsProps): TableColumn[] => { const docLinks = useKibana().services.docLinks; const actionsColumn = useActionsColumn(); - const enabledColumn = useEnabledColumn({ hasPermissions }); + const enabledColumn = useEnabledColumn({ hasCRUDPermissions }); const ruleNameColumn = useRuleNameColumn(); const { isInMemorySorting } = useRulesTableContext().state; const [showRelatedIntegrations] = useUiSetting$<boolean>(SHOW_RELATED_INTEGRATIONS_SETTING); @@ -425,13 +401,13 @@ export const useMonitoringColumns = ({ hasPermissions }: ColumnsProps): TableCol width: '16%', }, enabledColumn, - ...(hasPermissions ? [actionsColumn] : []), + ...(hasCRUDPermissions ? [actionsColumn] : []), ], [ actionsColumn, docLinks.links.siem.troubleshootGaps, enabledColumn, - hasPermissions, + hasCRUDPermissions, isInMemorySorting, ruleNameColumn, showRelatedIntegrations, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_has_actions_privileges.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_has_actions_privileges.ts similarity index 78% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_has_actions_privileges.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_has_actions_privileges.ts index c2acbed3158f1..fddf86e97646d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_has_actions_privileges.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_has_actions_privileges.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { useKibana } from '../../../../../common/lib/kibana'; -import { isBoolean } from '../../../../../common/utils/privileges'; +import { useKibana } from '../../../../common/lib/kibana'; +import { isBoolean } from '../../../../common/utils/privileges'; export const useHasActionsPrivileges = () => { const { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_has_ml_permissions.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_has_ml_permissions.ts similarity index 62% rename from x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_has_ml_permissions.ts rename to x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_has_ml_permissions.ts index c941ba7183b86..cedec36b33ebd 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/use_has_ml_permissions.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_has_ml_permissions.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { hasMlAdminPermissions } from '../../../../../../common/machine_learning/has_ml_admin_permissions'; -import { hasMlLicense } from '../../../../../../common/machine_learning/has_ml_license'; -import { useMlCapabilities } from '../../../../../common/components/ml/hooks/use_ml_capabilities'; +import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; +import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license'; +import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; export const useHasMlPermissions = () => { const mlCapabilities = useMlCapabilities(); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.tsx new file mode 100644 index 0000000000000..6e0e2bb761007 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_rules_table_actions.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 type { DefaultItemAction } from '@elastic/eui'; +import { EuiToolTip } from '@elastic/eui'; +import React from 'react'; +import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; +import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; +import { useKibana } from '../../../../common/lib/kibana'; +import { canEditRuleWithActions } from '../../../../common/utils/privileges'; +import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; +import type { Rule } from '../../../rule_management/logic'; +import { + downloadExportedRules, + useBulkExport, +} from '../../../rule_management/logic/bulk_actions/use_bulk_export'; +import { + goToRuleEditPage, + useExecuteBulkAction, +} from '../../../rule_management/logic/bulk_actions/use_execute_bulk_action'; +import { useRulesTableContext } from './rules_table/rules_table_context'; +import { useHasActionsPrivileges } from './use_has_actions_privileges'; + +export const useRulesTableActions = (): Array<DefaultItemAction<Rule>> => { + const { navigateToApp } = useKibana().services.application; + const hasActionsPrivileges = useHasActionsPrivileges(); + const toasts = useAppToasts(); + const { setLoadingRules } = useRulesTableContext().actions; + const { startTransaction } = useStartTransaction(); + const { executeBulkAction } = useExecuteBulkAction(); + const { bulkExport } = useBulkExport(); + + return [ + { + type: 'icon', + 'data-test-subj': 'editRuleAction', + description: i18n.EDIT_RULE_SETTINGS, + name: !hasActionsPrivileges ? ( + <EuiToolTip position="left" content={i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES}> + <>{i18n.EDIT_RULE_SETTINGS}</> + </EuiToolTip> + ) : ( + i18n.EDIT_RULE_SETTINGS + ), + icon: 'controlsHorizontal', + onClick: (rule: Rule) => goToRuleEditPage(rule.id, navigateToApp), + enabled: (rule: Rule) => canEditRuleWithActions(rule, hasActionsPrivileges), + }, + { + type: 'icon', + 'data-test-subj': 'duplicateRuleAction', + description: i18n.DUPLICATE_RULE, + icon: 'copy', + name: !hasActionsPrivileges ? ( + <EuiToolTip position="left" content={i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES}> + <>{i18n.DUPLICATE_RULE}</> + </EuiToolTip> + ) : ( + i18n.DUPLICATE_RULE + ), + enabled: (rule: Rule) => canEditRuleWithActions(rule, hasActionsPrivileges), + // TODO extract those handlers to hooks, like useDuplicateRule + onClick: async (rule: Rule) => { + startTransaction({ name: SINGLE_RULE_ACTIONS.DUPLICATE }); + const result = await executeBulkAction({ + action: BulkAction.duplicate, + setLoadingRules, + visibleRuleIds: [rule.id], + search: { ids: [rule.id] }, + }); + const createdRules = result?.attributes.results.created; + if (createdRules?.length) { + goToRuleEditPage(createdRules[0].id, navigateToApp); + } + }, + }, + { + type: 'icon', + 'data-test-subj': 'exportRuleAction', + description: i18n.EXPORT_RULE, + icon: 'exportAction', + name: i18n.EXPORT_RULE, + onClick: async (rule: Rule) => { + startTransaction({ name: SINGLE_RULE_ACTIONS.EXPORT }); + const response = await bulkExport({ + setLoadingRules, + visibleRuleIds: [rule.id], + search: { ids: [rule.id] }, + }); + if (response) { + await downloadExportedRules({ + response, + toasts, + }); + } + }, + enabled: (rule: Rule) => !rule.immutable, + }, + { + type: 'icon', + 'data-test-subj': 'deleteRuleAction', + description: i18n.DELETE_RULE, + icon: 'trash', + name: i18n.DELETE_RULE, + onClick: async (rule: Rule) => { + startTransaction({ name: SINGLE_RULE_ACTIONS.DELETE }); + await executeBulkAction({ + action: BulkAction.delete, + setLoadingRules, + visibleRuleIds: [rule.id], + search: { ids: [rule.id] }, + }); + }, + }, + ]; +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx new file mode 100644 index 0000000000000..194e16fdb8e4b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/pages/rule_management/index.tsx @@ -0,0 +1,163 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { EuiButton, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; + +import { APP_UI_ID } from '../../../../../common/constants'; +import { SecurityPageName } from '../../../../app/types'; +import { HeaderPage } from '../../../../common/components/header_page'; +import { ImportDataModal } from '../../../../common/components/import_data_modal'; +import { SecuritySolutionLinkButton } from '../../../../common/components/links'; +import { getDetectionEngineUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; +import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper'; +import { useBoolState } from '../../../../common/hooks/use_bool_state'; +import { useKibana } from '../../../../common/lib/kibana'; +import { hasUserCRUDPermission } from '../../../../common/utils/privileges'; +import { SpyRoute } from '../../../../common/utils/route/spy_routes'; + +import { MissingPrivilegesCallOut } from '../../../../detections/components/callouts/missing_privileges_callout'; +import { MlJobCompatibilityCallout } from '../../../../detections/components/callouts/ml_job_compatibility_callout'; +import { NeedAdminForUpdateRulesCallOut } from '../../../../detections/components/callouts/need_admin_for_update_callout'; +import { LoadPrePackagedRules } from '../../../../detections/components/rules/pre_packaged_rules/load_prepackaged_rules'; +import { LoadPrePackagedRulesButton } from '../../../../detections/components/rules/pre_packaged_rules/load_prepackaged_rules_button'; +import { UpdatePrePackagedRulesCallOut } from '../../../../detections/components/rules/pre_packaged_rules/update_callout'; +import { ValueListsFlyout } from '../../../../detections/components/value_lists_management_flyout'; +import { useUserData } from '../../../../detections/components/user_info'; +import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; +import { redirectToDetections } from '../../../../detections/pages/detection_engine/rules/helpers'; + +import { useInvalidateFindRulesQuery } from '../../../rule_management/api/hooks/use_find_rules_query'; +import { importRules } from '../../../rule_management/logic'; +import { usePrePackagedRulesInstallationStatus } from '../../../rule_management/logic/use_pre_packaged_rules_installation_status'; +import { usePrePackagedTimelinesInstallationStatus } from '../../../rule_management/logic/use_pre_packaged_timelines_installation_status'; + +import { AllRules } from '../../components/rules_table'; +import { RulesTableContextProvider } from '../../components/rules_table/rules_table/rules_table_context'; + +import * as i18n from '../../../../detections/pages/detection_engine/rules/translations'; + +const RulesPageComponent: React.FC = () => { + const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState(); + const [isValueListFlyoutVisible, showValueListFlyout, hideValueListFlyout] = useBoolState(); + const { navigateToApp } = useKibana().services.application; + const invalidateFindRulesQuery = useInvalidateFindRulesQuery(); + + const [ + { + loading: userInfoLoading, + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + canUserCRUD, + }, + ] = useUserData(); + const { + loading: listsConfigLoading, + canWriteIndex: canWriteListsIndex, + needsConfiguration: needsListsConfiguration, + } = useListsConfig(); + const loading = userInfoLoading || listsConfigLoading; + const prePackagedRuleStatus = usePrePackagedRulesInstallationStatus(); + const prePackagedTimelineStatus = usePrePackagedTimelinesInstallationStatus(); + + if ( + redirectToDetections( + isSignalIndexExists, + isAuthenticated, + hasEncryptionKey, + needsListsConfiguration + ) + ) { + navigateToApp(APP_UI_ID, { + deepLinkId: SecurityPageName.alerts, + path: getDetectionEngineUrl(), + }); + return null; + } + + return ( + <> + <NeedAdminForUpdateRulesCallOut /> + <MissingPrivilegesCallOut /> + <MlJobCompatibilityCallout /> + <ValueListsFlyout showFlyout={isValueListFlyoutVisible} onClose={hideValueListFlyout} /> + <ImportDataModal + checkBoxLabel={i18n.OVERWRITE_WITH_SAME_NAME} + closeModal={hideImportModal} + description={i18n.SELECT_RULE} + errorMessage={i18n.IMPORT_FAILED} + failedDetailed={i18n.IMPORT_FAILED_DETAILED} + importComplete={invalidateFindRulesQuery} + importData={importRules} + successMessage={i18n.SUCCESSFULLY_IMPORTED_RULES} + showModal={isImportModalVisible} + submitBtnText={i18n.IMPORT_RULE_BTN_TITLE} + subtitle={i18n.INITIAL_PROMPT_TEXT} + title={i18n.IMPORT_RULE} + showExceptionsCheckBox + showCheckBox + /> + + <RulesTableContextProvider> + <SecuritySolutionPageWrapper> + <HeaderPage title={i18n.PAGE_TITLE}> + <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}> + <EuiFlexItem grow={false}> + <LoadPrePackagedRules> + {(renderProps) => <LoadPrePackagedRulesButton {...renderProps} />} + </LoadPrePackagedRules> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiToolTip position="top" content={i18n.UPLOAD_VALUE_LISTS_TOOLTIP}> + <EuiButton + data-test-subj="open-value-lists-modal-button" + iconType="importAction" + isDisabled={!canWriteListsIndex || !canUserCRUD || loading} + onClick={showValueListFlyout} + > + {i18n.IMPORT_VALUE_LISTS} + </EuiButton> + </EuiToolTip> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButton + data-test-subj="rules-import-modal-button" + iconType="importAction" + isDisabled={!hasUserCRUDPermission(canUserCRUD) || loading} + onClick={showImportModal} + > + {i18n.IMPORT_RULE} + </EuiButton> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <SecuritySolutionLinkButton + data-test-subj="create-new-rule" + fill + iconType="plusInCircle" + isDisabled={!hasUserCRUDPermission(canUserCRUD) || loading} + deepLinkId={SecurityPageName.rulesCreate} + > + {i18n.ADD_NEW_RULE} + </SecuritySolutionLinkButton> + </EuiFlexItem> + </EuiFlexGroup> + </HeaderPage> + {(prePackagedRuleStatus === 'ruleNeedUpdate' || + prePackagedTimelineStatus === 'timelineNeedUpdate') && ( + <UpdatePrePackagedRulesCallOut data-test-subj="update-callout-button" /> + )} + <AllRules data-test-subj="all-rules" /> + </SecuritySolutionPageWrapper> + </RulesTableContextProvider> + + <SpyRoute pageName={SecurityPageName.rules} /> + </> + ); +}; + +export const RulesPage = React.memo(RulesPageComponent); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/index.test.tsx index ec4fdb5cb6e8d..9155415165c5e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/additional_filters_action/index.test.tsx @@ -11,7 +11,7 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { AdditionalFiltersAction } from '.'; import { TestProviders } from '../../../../common/mock/test_providers'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); jest.mock('../../../../common/lib/kibana'); describe('AdditionalFiltersAction', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 14c55ef74d186..e6756cf599929 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -177,7 +177,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({ id: tableId, loadingText: i18n.LOADING_ALERTS, queryFields: requiredFieldsForActions, - title: '', + title: i18n.ALERTS_DOCUMENT_TYPE, showCheckboxes: true, }) ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 03141dfb02f57..842cdfe82fffb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -42,7 +42,7 @@ import { ATTACH_ALERT_TO_CASE_FOR_ROW } from '../../../../timelines/components/t import { useEventFilterAction } from './use_event_filter_action'; import { useAddToCaseActions } from './use_add_to_case_actions'; import { isAlertFromEndpointAlert } from '../../../../common/utils/endpoint_alert_check'; -import type { Rule } from '../../../containers/detection_engine/rules/types'; +import type { Rule } from '../../../../detection_engine/rule_management/logic/types'; interface AlertContextMenuProps { ariaLabel?: string; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx index f084bb68311a3..cf9711c58f8cb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx @@ -36,7 +36,8 @@ import * as i18nRiskScore from '../risk_score_mapping/translations'; import type { RequiredFieldArray, Threshold, -} from '../../../../../common/detection_engine/schemas/common'; +} from '../../../../../common/detection_engine/rule_schema'; + import * as i18n from './translations'; import type { BuildQueryBarDescription, BuildThreatDescription, ListItems } from './types'; import { SeverityBadge } from '../severity_badge'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx index 63d2c52323583..aeba71acb6ab8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx @@ -21,7 +21,7 @@ import { FilterStateStore } from '@kbn/es-query'; import { mockAboutStepRule, mockDefineStepRule, -} from '../../../pages/detection_engine/rules/all/__mocks__/mock'; +} from '../../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock'; import { coreMock } from '@kbn/core/public/mocks'; import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx index ba0359c4fda3e..92879c56e9885 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx @@ -18,7 +18,7 @@ import { buildRelatedIntegrationsDescription } from '../related_integrations/int import type { RelatedIntegrationArray, RequiredFieldArray, -} from '../../../../../common/detection_engine/schemas/common'; +} from '../../../../../common/detection_engine/rule_schema'; import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; import type { EqlOptionsSelected } from '../../../../../common/search_strategy'; import { useKibana } from '../../../../common/lib/kibana'; @@ -48,7 +48,7 @@ import { buildMlJobsDescription } from './ml_job_description'; import { buildActionsDescription } from './actions_description'; import { buildThrottleDescription } from './throttle_description'; import { THREAT_QUERY_LABEL } from './translations'; -import { filterEmptyThreats } from '../../../pages/detection_engine/rules/create/helpers'; +import { filterEmptyThreats } from '../../../../detection_engine/rule_creation_ui/pages/rule_creation/helpers'; const DescriptionListContainer = styled(EuiDescriptionList)` &.euiDescriptionList--column .euiDescriptionList__title { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/ml_job_description.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/ml_job_description.tsx index c9dcb19c64e81..b5342934302b3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/ml_job_description.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/ml_job_description.tsx @@ -73,7 +73,7 @@ const Wrapper = styled.div` `; const MlJobDescriptionComponent: React.FC<{ jobId: string }> = ({ jobId }) => { - const { jobs } = useSecurityJobs(false); + const { jobs } = useSecurityJobs(); const { services: { http, ml }, } = useKibana(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx index f30fd074f0d45..51e1e0683d766 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; import { mockIndexPattern, TestProviders, useFormFieldMock } from '../../../../common/mock'; -import { mockQueryBar } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; +import { mockQueryBar } from '../../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock'; import type { EqlQueryBarProps } from './eql_query_bar'; import { EqlQueryBar } from './eql_query_bar'; import { getEqlValidationError } from './validators.mock'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts index 74f1bb89bb8c5..a4e7baf3c30be 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.test.ts @@ -7,7 +7,7 @@ import { debounceAsync } from './validators'; -jest.useFakeTimers(); +jest.useFakeTimers('legacy'); describe('debounceAsync', () => { let fn: jest.Mock; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx index d77b52a227cdf..a067930ca78ed 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/ml_job_select/index.tsx @@ -67,7 +67,7 @@ const renderJobOption = (option: MlJobOption) => ( export const MlJobSelect: React.FC<MlJobSelectProps> = ({ describedByIds = [], field }) => { const jobIds = field.value as string[]; const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - const { loading, jobs } = useSecurityJobs(false); + const { loading, jobs } = useSecurityJobs(); const mlUrl = useKibana().services.application.getUrlForApp('ml'); const handleJobSelect = useCallback( (selectedJobOptions: MlJobOption[]): void => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx index b33281a165b8d..bd1b951a7002f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx @@ -13,7 +13,7 @@ import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; import { TestProviders } from '../../../../common/mock'; import '../../../../common/mock/match_media'; -import { getPrePackagedRulesStatus } from '../../../containers/detection_engine/rules/api'; +import { getPrePackagedRulesStatus } from '../../../../detection_engine/rule_management/api/api'; import { PrePackagedRulesPrompt } from './load_empty_prompt'; jest.mock('react-router-dom', () => { @@ -43,7 +43,7 @@ jest.mock('../../../../common/lib/kibana/kibana_react', () => { }; }); -jest.mock('../../../containers/detection_engine/rules/api', () => ({ +jest.mock('../../../../detection_engine/rule_management/api/api', () => ({ getPrePackagedRulesStatus: jest.fn().mockResolvedValue({ rules_not_installed: 0, rules_installed: 0, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx index 252787c9dd853..f44008622eb9e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx @@ -6,17 +6,15 @@ */ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React, { memo, useCallback, useMemo, useState } from 'react'; +import React, { memo } from 'react'; import styled from 'styled-components'; -import { affectedJobIds } from '../../callouts/ml_job_compatibility_callout/affected_job_ids'; -import { MlJobUpgradeModal } from '../../modals/ml_job_upgrade_modal'; -import { useInstalledSecurityJobs } from '../../../../common/components/ml/hooks/use_installed_security_jobs'; - -import * as i18n from './translations'; -import { SecuritySolutionLinkButton } from '../../../../common/components/links'; import { SecurityPageName } from '../../../../app/types'; -import { usePrePackagedRules } from '../../../containers/detection_engine/rules'; +import { SecuritySolutionLinkButton } from '../../../../common/components/links'; +import { hasUserCRUDPermission } from '../../../../common/utils/privileges'; import { useUserData } from '../../user_info'; +import { LoadPrePackagedRules } from './load_prepackaged_rules'; +import { LoadPrePackagedRulesButton } from './load_prepackaged_rules_button'; +import * as i18n from './translations'; const EmptyPrompt = styled(EuiEmptyPrompt)` align-self: center; /* Corrects horizontal centering in IE11 */ @@ -24,62 +22,9 @@ const EmptyPrompt = styled(EuiEmptyPrompt)` EmptyPrompt.displayName = 'EmptyPrompt'; -interface PrePackagedRulesPromptProps { - createPrePackagedRules: () => void; - loading: boolean; - userHasPermissions: boolean; -} - -const PrePackagedRulesPromptComponent: React.FC<PrePackagedRulesPromptProps> = ({ - createPrePackagedRules, - loading = false, - userHasPermissions = false, -}) => { - const [{ isSignalIndexExists, isAuthenticated, hasEncryptionKey, canUserCRUD, hasIndexWrite }] = - useUserData(); - - const { loading: loadingJobs, jobs } = useInstalledSecurityJobs(); - const legacyJobsInstalled = jobs.filter((job) => affectedJobIds.includes(job.id)); - const [isUpgradeModalVisible, setIsUpgradeModalVisible] = useState(false); - - const { getLoadPrebuiltRulesAndTemplatesButton } = usePrePackagedRules({ - canUserCRUD, - hasIndexWrite, - isSignalIndexExists, - isAuthenticated, - hasEncryptionKey, - }); - - // Wrapper to add confirmation modal for users who may be running older ML Jobs that would - // be overridden by updating their rules. For details, see: https://github.com/elastic/kibana/issues/128121 - const mlJobUpgradeModalConfirm = useCallback(() => { - setIsUpgradeModalVisible(false); - createPrePackagedRules(); - }, [createPrePackagedRules, setIsUpgradeModalVisible]); - - const loadPrebuiltRulesAndTemplatesButton = useMemo( - () => - getLoadPrebuiltRulesAndTemplatesButton({ - isDisabled: !userHasPermissions || loading || loadingJobs, - onClick: () => { - if (legacyJobsInstalled.length > 0) { - setIsUpgradeModalVisible(true); - } else { - createPrePackagedRules(); - } - }, - fill: true, - 'data-test-subj': 'load-prebuilt-rules', - }), - [ - getLoadPrebuiltRulesAndTemplatesButton, - userHasPermissions, - loading, - loadingJobs, - legacyJobsInstalled, - createPrePackagedRules, - ] - ); +const PrePackagedRulesPromptComponent = () => { + const [{ canUserCRUD }] = useUserData(); + const hasPermissions = hasUserCRUDPermission(canUserCRUD); return ( <EmptyPrompt @@ -88,23 +33,26 @@ const PrePackagedRulesPromptComponent: React.FC<PrePackagedRulesPromptProps> = ( body={<p>{i18n.PRE_BUILT_MSG}</p>} actions={ <EuiFlexGroup justifyContent="center"> - <EuiFlexItem grow={false}>{loadPrebuiltRulesAndTemplatesButton}</EuiFlexItem> + <EuiFlexItem grow={false}> + <LoadPrePackagedRules> + {(renderProps) => ( + <LoadPrePackagedRulesButton + fill + data-test-subj="load-prebuilt-rules" + {...renderProps} + /> + )} + </LoadPrePackagedRules> + </EuiFlexItem> <EuiFlexItem grow={false}> <SecuritySolutionLinkButton - isDisabled={!userHasPermissions} + isDisabled={!hasPermissions} iconType="plusInCircle" deepLinkId={SecurityPageName.rulesCreate} > {i18n.CREATE_RULE_ACTION} </SecuritySolutionLinkButton> </EuiFlexItem> - {isUpgradeModalVisible && ( - <MlJobUpgradeModal - jobs={legacyJobsInstalled} - onCancel={() => setIsUpgradeModalVisible(false)} - onConfirm={mlJobUpgradeModalConfirm} - /> - )} </EuiFlexGroup> } /> diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_prepackaged_rules.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_prepackaged_rules.tsx new file mode 100644 index 0000000000000..e5d05d7e7fbb0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_prepackaged_rules.tsx @@ -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 React, { useCallback } from 'react'; +import { useInstalledSecurityJobs } from '../../../../common/components/ml/hooks/use_installed_security_jobs'; +import { useBoolState } from '../../../../common/hooks/use_bool_state'; +import { RULES_TABLE_ACTIONS } from '../../../../common/lib/apm/user_actions'; +import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; +import { useCreatePrePackagedRules } from '../../../../detection_engine/rule_management/logic/use_create_pre_packaged_rules'; +import { usePrePackagedRulesStatus } from '../../../../detection_engine/rule_management/logic/use_pre_packaged_rules_status'; +import { affectedJobIds } from '../../callouts/ml_job_compatibility_callout/affected_job_ids'; +import { MlJobUpgradeModal } from '../../modals/ml_job_upgrade_modal'; + +interface LoadPrePackagedRulesRenderProps { + isLoading: boolean; + isDisabled: boolean; + onClick: () => Promise<void>; +} + +interface LoadPrePackagedRulesProps { + children: (renderProps: LoadPrePackagedRulesRenderProps) => React.ReactNode; +} + +export const LoadPrePackagedRules = ({ children }: LoadPrePackagedRulesProps) => { + const { isFetching: isFetchingPrepackagedStatus } = usePrePackagedRulesStatus(); + const { + createPrePackagedRules, + canCreatePrePackagedRules, + isLoading: loadingCreatePrePackagedRules, + } = useCreatePrePackagedRules(); + + const { startTransaction } = useStartTransaction(); + const handleCreatePrePackagedRules = useCallback(async () => { + startTransaction({ name: RULES_TABLE_ACTIONS.LOAD_PREBUILT }); + await createPrePackagedRules(); + }, [createPrePackagedRules, startTransaction]); + + const [isUpgradeModalVisible, showUpgradeModal, hideUpgradeModal] = useBoolState(false); + const { loading: loadingJobs, jobs } = useInstalledSecurityJobs(); + const legacyJobsInstalled = jobs.filter((job) => affectedJobIds.includes(job.id)); + + const handleInstallPrePackagedRules = useCallback(async () => { + if (legacyJobsInstalled.length > 0) { + showUpgradeModal(); + } else { + await handleCreatePrePackagedRules(); + } + }, [handleCreatePrePackagedRules, legacyJobsInstalled.length, showUpgradeModal]); + + // Wrapper to add confirmation modal for users who may be running older ML Jobs that would + // be overridden by updating their rules. For details, see: https://github.com/elastic/kibana/issues/128121 + const mlJobUpgradeModalConfirm = useCallback(() => { + hideUpgradeModal(); + handleCreatePrePackagedRules(); + }, [handleCreatePrePackagedRules, hideUpgradeModal]); + + const isDisabled = !canCreatePrePackagedRules || isFetchingPrepackagedStatus || loadingJobs; + + return ( + <> + {children({ + isLoading: loadingCreatePrePackagedRules, + isDisabled, + onClick: handleInstallPrePackagedRules, + })} + {isUpgradeModalVisible && ( + <MlJobUpgradeModal + jobs={legacyJobsInstalled} + onCancel={() => hideUpgradeModal()} + onConfirm={mlJobUpgradeModalConfirm} + /> + )} + </> + ); +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_prepackaged_rules_button.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_prepackaged_rules_button.tsx new file mode 100644 index 0000000000000..996bbc386be37 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_prepackaged_rules_button.tsx @@ -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. + */ + +import { EuiButton } from '@elastic/eui'; +import React from 'react'; +import { usePrePackagedRulesInstallationStatus } from '../../../../detection_engine/rule_management/logic/use_pre_packaged_rules_installation_status'; +import { usePrePackagedRulesStatus } from '../../../../detection_engine/rule_management/logic/use_pre_packaged_rules_status'; +import { usePrePackagedTimelinesInstallationStatus } from '../../../../detection_engine/rule_management/logic/use_pre_packaged_timelines_installation_status'; +import type { + PrePackagedRuleInstallationStatus, + PrePackagedTimelineInstallationStatus, +} from '../../../pages/detection_engine/rules/helpers'; +import * as i18n from './translations'; + +const getLoadRulesOrTimelinesButtonTitle = ( + rulesStatus: PrePackagedRuleInstallationStatus, + timelineStatus: PrePackagedTimelineInstallationStatus +) => { + if (rulesStatus === 'ruleNotInstalled' && timelineStatus === 'timelinesNotInstalled') + return i18n.LOAD_PREPACKAGED_RULES_AND_TEMPLATES; + else if (rulesStatus === 'ruleNotInstalled' && timelineStatus !== 'timelinesNotInstalled') + return i18n.LOAD_PREPACKAGED_RULES; + else if (rulesStatus !== 'ruleNotInstalled' && timelineStatus === 'timelinesNotInstalled') + return i18n.LOAD_PREPACKAGED_TIMELINE_TEMPLATES; +}; + +const getMissingRulesOrTimelinesButtonTitle = (missingRules: number, missingTimelines: number) => { + if (missingRules > 0 && missingTimelines === 0) + return i18n.RELOAD_MISSING_PREPACKAGED_RULES(missingRules); + else if (missingRules === 0 && missingTimelines > 0) + return i18n.RELOAD_MISSING_PREPACKAGED_TIMELINES(missingTimelines); + else if (missingRules > 0 && missingTimelines > 0) + return i18n.RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES(missingRules, missingTimelines); +}; + +interface LoadPrePackagedRulesButtonProps { + fill?: boolean; + 'data-test-subj'?: string; + isLoading: boolean; + isDisabled: boolean; + onClick: () => Promise<void>; +} + +export const LoadPrePackagedRulesButton = ({ + fill, + 'data-test-subj': dataTestSubj = 'loadPrebuiltRulesBtn', + isLoading, + isDisabled, + onClick, +}: LoadPrePackagedRulesButtonProps) => { + const { data: prePackagedRulesStatus } = usePrePackagedRulesStatus(); + const prePackagedAssetsStatus = usePrePackagedRulesInstallationStatus(); + const prePackagedTimelineStatus = usePrePackagedTimelinesInstallationStatus(); + + const showInstallButton = + (prePackagedAssetsStatus === 'ruleNotInstalled' || + prePackagedTimelineStatus === 'timelinesNotInstalled') && + prePackagedAssetsStatus !== 'someRuleUninstall'; + + if (showInstallButton) { + return ( + <EuiButton + fill={fill} + iconType="indexOpen" + isLoading={isLoading} + isDisabled={isDisabled} + onClick={onClick} + data-test-subj={dataTestSubj} + > + {getLoadRulesOrTimelinesButtonTitle(prePackagedAssetsStatus, prePackagedTimelineStatus)} + </EuiButton> + ); + } + + const showUpdateButton = + prePackagedAssetsStatus === 'someRuleUninstall' || + prePackagedTimelineStatus === 'someTimelineUninstall'; + + if (showUpdateButton) { + return ( + <EuiButton + fill={fill} + iconType="plusInCircle" + isLoading={isLoading} + isDisabled={isDisabled} + onClick={onClick} + data-test-subj={dataTestSubj} + > + {getMissingRulesOrTimelinesButtonTitle( + prePackagedRulesStatus?.rules_not_installed ?? 0, + prePackagedRulesStatus?.timelines_not_installed ?? 0 + )} + </EuiButton> + ); + } + + return null; +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts index 5b66b4611c03d..b1207b59faf8b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/translations.ts @@ -96,3 +96,57 @@ export const RELEASE_NOTES_HELP = i18n.translate( defaultMessage: 'Release notes', } ); + +export const LOAD_PREPACKAGED_RULES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.loadPrePackagedRulesButton', + { + defaultMessage: 'Load Elastic prebuilt rules', + } +); + +export const LOAD_PREPACKAGED_TIMELINE_TEMPLATES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.loadPrePackagedTimelineTemplatesButton', + { + defaultMessage: 'Load Elastic prebuilt timeline templates', + } +); + +export const LOAD_PREPACKAGED_RULES_AND_TEMPLATES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.loadPrePackagedRulesAndTemplatesButton', + { + defaultMessage: 'Load Elastic prebuilt rules and timeline templates', + } +); + +export const RELOAD_MISSING_PREPACKAGED_RULES = (missingRules: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesButton', + { + values: { missingRules }, + defaultMessage: + 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ', + } + ); + +export const RELOAD_MISSING_PREPACKAGED_TIMELINES = (missingTimelines: number) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedTimelinesButton', + { + values: { missingTimelines }, + defaultMessage: + 'Install {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ', + } + ); + +export const RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES = ( + missingRules: number, + missingTimelines: number +) => + i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesAndTimelinesButton', + { + values: { missingRules, missingTimelines }, + defaultMessage: + 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} and {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ', + } + ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx index 70b98eb22fd89..6d882fe2930a3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.test.tsx @@ -5,13 +5,18 @@ * 2.0. */ +import { render } from '@testing-library/react'; import React from 'react'; -import { shallow } from 'enzyme'; - -import { UpdatePrePackagedRulesCallOut } from './update_callout'; import { useKibana } from '../../../../common/lib/kibana'; +import { TestProviders } from '../../../../common/mock'; +import { useFetchPrebuiltRulesStatusQuery } from '../../../../detection_engine/rule_management/api/hooks/use_fetch_prebuilt_rules_status_query'; +import { mockReactQueryResponse } from '../../../../detection_engine/rule_management/api/hooks/__mocks__/mock_react_query_response'; +import { UpdatePrePackagedRulesCallOut } from './update_callout'; jest.mock('../../../../common/lib/kibana'); +jest.mock( + '../../../../detection_engine/rule_management/api/hooks/use_fetch_prebuilt_rules_status_query' +); describe('UpdatePrePackagedRulesCallOut', () => { beforeAll(() => { @@ -28,105 +33,134 @@ describe('UpdatePrePackagedRulesCallOut', () => { }); }); - it('renders correctly', () => { - const wrapper = shallow( - <UpdatePrePackagedRulesCallOut - loading={false} - numberOfUpdatedRules={0} - numberOfUpdatedTimelines={0} - updateRules={jest.fn()} - /> - ); - - expect(wrapper.find('EuiCallOut')).toHaveLength(1); - }); - it('renders callOutMessage correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines = 0', () => { - const wrapper = shallow( - <UpdatePrePackagedRulesCallOut - loading={false} - numberOfUpdatedRules={1} - numberOfUpdatedTimelines={0} - updateRules={jest.fn()} - /> + (useFetchPrebuiltRulesStatusQuery as jest.Mock).mockReturnValue( + mockReactQueryResponse({ + data: { + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: 1, + timelines_updated: 0, + timelines_not_installed: 0, + timelines_not_updated: 0, + }, + }) ); - expect(wrapper.find('[data-test-subj="update-callout"]').find('p').text()).toEqual( + const { getByTestId } = render(<UpdatePrePackagedRulesCallOut />, { wrapper: TestProviders }); + + expect(getByTestId('update-callout')).toHaveTextContent( 'You can update 1 Elastic prebuilt ruleRelease notes' ); }); it('renders buttonTitle correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines = 0', () => { - const wrapper = shallow( - <UpdatePrePackagedRulesCallOut - loading={false} - numberOfUpdatedRules={1} - numberOfUpdatedTimelines={0} - updateRules={jest.fn()} - /> + (useFetchPrebuiltRulesStatusQuery as jest.Mock).mockReturnValue( + mockReactQueryResponse({ + data: { + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: 1, + timelines_updated: 0, + timelines_not_installed: 0, + timelines_not_updated: 0, + }, + }) ); - expect(wrapper.find('[data-test-subj="update-callout-button"]').prop('children')).toEqual( + const { getByTestId } = render(<UpdatePrePackagedRulesCallOut />, { wrapper: TestProviders }); + + expect(getByTestId('update-callout-button')).toHaveTextContent( 'Update 1 Elastic prebuilt rule' ); }); it('renders callOutMessage correctly: numberOfUpdatedRules = 0 and numberOfUpdatedTimelines > 0', () => { - const wrapper = shallow( - <UpdatePrePackagedRulesCallOut - loading={false} - numberOfUpdatedRules={0} - numberOfUpdatedTimelines={1} - updateRules={jest.fn()} - /> + (useFetchPrebuiltRulesStatusQuery as jest.Mock).mockReturnValue( + mockReactQueryResponse({ + data: { + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: 0, + timelines_updated: 0, + timelines_not_installed: 0, + timelines_not_updated: 1, + }, + }) ); - expect(wrapper.find('[data-test-subj="update-callout"]').find('p').text()).toEqual( + const { getByTestId } = render(<UpdatePrePackagedRulesCallOut />, { wrapper: TestProviders }); + + expect(getByTestId('update-callout')).toHaveTextContent( 'You can update 1 Elastic prebuilt timelineRelease notes' ); }); it('renders buttonTitle correctly: numberOfUpdatedRules = 0 and numberOfUpdatedTimelines > 0', () => { - const wrapper = shallow( - <UpdatePrePackagedRulesCallOut - loading={false} - numberOfUpdatedRules={0} - numberOfUpdatedTimelines={1} - updateRules={jest.fn()} - /> + (useFetchPrebuiltRulesStatusQuery as jest.Mock).mockReturnValue( + mockReactQueryResponse({ + data: { + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: 0, + timelines_updated: 0, + timelines_not_installed: 0, + timelines_not_updated: 1, + }, + }) ); - expect(wrapper.find('[data-test-subj="update-callout-button"]').prop('children')).toEqual( + const { getByTestId } = render(<UpdatePrePackagedRulesCallOut />, { wrapper: TestProviders }); + + expect(getByTestId('update-callout-button')).toHaveTextContent( 'Update 1 Elastic prebuilt timeline' ); }); it('renders callOutMessage correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines > 0', () => { - const wrapper = shallow( - <UpdatePrePackagedRulesCallOut - loading={false} - numberOfUpdatedRules={1} - numberOfUpdatedTimelines={1} - updateRules={jest.fn()} - /> + (useFetchPrebuiltRulesStatusQuery as jest.Mock).mockReturnValue( + mockReactQueryResponse({ + data: { + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: 1, + timelines_updated: 0, + timelines_not_installed: 0, + timelines_not_updated: 1, + }, + }) ); - expect(wrapper.find('[data-test-subj="update-callout"]').find('p').text()).toEqual( + const { getByTestId } = render(<UpdatePrePackagedRulesCallOut />, { wrapper: TestProviders }); + + expect(getByTestId('update-callout')).toHaveTextContent( 'You can update 1 Elastic prebuilt rule and 1 Elastic prebuilt timeline. Note that this will reload deleted Elastic prebuilt rules.Release notes' ); }); it('renders buttonTitle correctly: numberOfUpdatedRules > 0 and numberOfUpdatedTimelines > 0', () => { - const wrapper = shallow( - <UpdatePrePackagedRulesCallOut - loading={false} - numberOfUpdatedRules={1} - numberOfUpdatedTimelines={1} - updateRules={jest.fn()} - /> + (useFetchPrebuiltRulesStatusQuery as jest.Mock).mockReturnValue( + mockReactQueryResponse({ + data: { + rules_custom_installed: 0, + rules_installed: 0, + rules_not_installed: 0, + rules_not_updated: 1, + timelines_updated: 0, + timelines_not_installed: 0, + timelines_not_updated: 1, + }, + }) ); - expect(wrapper.find('[data-test-subj="update-callout-button"]').prop('children')).toEqual( + const { getByTestId } = render(<UpdatePrePackagedRulesCallOut />, { wrapper: TestProviders }); + + expect(getByTestId('update-callout-button')).toHaveTextContent( 'Update 1 Elastic prebuilt rule and 1 Elastic prebuilt timeline' ); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx index 9d67a1e6b9ae8..1526c211990e3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/update_callout.tsx @@ -5,51 +5,42 @@ * 2.0. */ +import { EuiButton, EuiCallOut, EuiLink } from '@elastic/eui'; import React, { memo, useMemo } from 'react'; - -import { EuiCallOut, EuiButton, EuiLink } from '@elastic/eui'; - import { useKibana } from '../../../../common/lib/kibana'; +import { usePrePackagedRulesStatus } from '../../../../detection_engine/rule_management/logic/use_pre_packaged_rules_status'; +import { LoadPrePackagedRules } from './load_prepackaged_rules'; import * as i18n from './translations'; -interface UpdatePrePackagedRulesCallOutProps { - loading: boolean; - numberOfUpdatedRules: number; - numberOfUpdatedTimelines: number; - updateRules: () => void; -} - -const UpdatePrePackagedRulesCallOutComponent: React.FC<UpdatePrePackagedRulesCallOutProps> = ({ - loading, - numberOfUpdatedRules, - numberOfUpdatedTimelines, - updateRules, -}) => { +const UpdatePrePackagedRulesCallOutComponent = () => { const { services } = useKibana(); + const { data: prePackagedRulesStatus } = usePrePackagedRulesStatus(); + const rulesNotUpdated = prePackagedRulesStatus?.rules_not_updated ?? 0; + const timelinesNotUpdated = prePackagedRulesStatus?.timelines_not_updated ?? 0; const prepackagedRulesOrTimelines = useMemo(() => { - if (numberOfUpdatedRules > 0 && numberOfUpdatedTimelines === 0) { + if (rulesNotUpdated > 0 && timelinesNotUpdated === 0) { return { - callOutMessage: i18n.UPDATE_PREPACKAGED_RULES_MSG(numberOfUpdatedRules), - buttonTitle: i18n.UPDATE_PREPACKAGED_RULES(numberOfUpdatedRules), + callOutMessage: i18n.UPDATE_PREPACKAGED_RULES_MSG(rulesNotUpdated), + buttonTitle: i18n.UPDATE_PREPACKAGED_RULES(rulesNotUpdated), }; - } else if (numberOfUpdatedRules === 0 && numberOfUpdatedTimelines > 0) { + } else if (rulesNotUpdated === 0 && timelinesNotUpdated > 0) { return { - callOutMessage: i18n.UPDATE_PREPACKAGED_TIMELINES_MSG(numberOfUpdatedTimelines), - buttonTitle: i18n.UPDATE_PREPACKAGED_TIMELINES(numberOfUpdatedTimelines), + callOutMessage: i18n.UPDATE_PREPACKAGED_TIMELINES_MSG(timelinesNotUpdated), + buttonTitle: i18n.UPDATE_PREPACKAGED_TIMELINES(timelinesNotUpdated), }; - } else if (numberOfUpdatedRules > 0 && numberOfUpdatedTimelines > 0) + } else if (rulesNotUpdated > 0 && timelinesNotUpdated > 0) return { callOutMessage: i18n.UPDATE_PREPACKAGED_RULES_AND_TIMELINES_MSG( - numberOfUpdatedRules, - numberOfUpdatedTimelines + rulesNotUpdated, + timelinesNotUpdated ), buttonTitle: i18n.UPDATE_PREPACKAGED_RULES_AND_TIMELINES( - numberOfUpdatedRules, - numberOfUpdatedTimelines + rulesNotUpdated, + timelinesNotUpdated ), }; - }, [numberOfUpdatedRules, numberOfUpdatedTimelines]); + }, [rulesNotUpdated, timelinesNotUpdated]); return ( <EuiCallOut title={i18n.UPDATE_PREPACKAGED_RULES_TITLE} data-test-subj="update-callout"> @@ -60,14 +51,13 @@ const UpdatePrePackagedRulesCallOutComponent: React.FC<UpdatePrePackagedRulesCal {i18n.RELEASE_NOTES_HELP} </EuiLink> </p> - <EuiButton - onClick={updateRules} - size="s" - isLoading={loading} - data-test-subj="update-callout-button" - > - {prepackagedRulesOrTimelines?.buttonTitle} - </EuiButton> + <LoadPrePackagedRules> + {(renderProps) => ( + <EuiButton size="s" data-test-subj="update-callout-button" {...renderProps}> + {prepackagedRulesOrTimelines?.buttonTitle} + </EuiButton> + )} + </LoadPrePackagedRules> </EuiCallOut> ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.ts b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.ts index b8475f7a0e9af..361e542fa3f0f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integration_details.ts @@ -7,12 +7,15 @@ import { capitalize } from 'lodash'; import semver from 'semver'; + import type { InstalledIntegration, InstalledIntegrationArray, +} from '../../../../../common/detection_engine/fleet_integrations'; +import type { RelatedIntegration, RelatedIntegrationArray, -} from '../../../../../common/detection_engine/schemas/common'; +} from '../../../../../common/detection_engine/rule_schema'; export interface IntegrationDetails { packageName: string; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/index.tsx index 5932faf1de6f0..5640abea69cc3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import styled from 'styled-components'; -import type { RelatedIntegrationArray } from '../../../../../../common/detection_engine/schemas/common'; +import type { RelatedIntegrationArray } from '../../../../../../common/detection_engine/rule_schema'; import type { ListItems } from '../../description_step/types'; import type { IntegrationDetails } from '../integration_details'; import { useRelatedIntegrations } from '../use_related_integrations'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx index 6c17b182381ca..36fc206aa92ea 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_popover/index.tsx @@ -16,7 +16,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import type { RelatedIntegrationArray } from '../../../../../../common/detection_engine/schemas/common'; +import type { RelatedIntegrationArray } from '../../../../../../common/detection_engine/rule_schema'; import { IntegrationDescription } from '../integrations_description'; import { useRelatedIntegrations } from '../use_related_integrations'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/mock.ts b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/mock.ts index 786e33ad69293..43a0c8a0602ef 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RelatedIntegrationArray } from '../../../../../common/detection_engine/schemas/common'; +import type { RelatedIntegrationArray } from '../../../../../common/detection_engine/rule_schema'; export const relatedIntegrations: RelatedIntegrationArray = [ { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.test.tsx index 705c7f5f3fbd3..d1322fd76a91f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.test.tsx @@ -5,18 +5,18 @@ * 2.0. */ -jest.mock('../../../containers/detection_engine/rules/api'); -jest.mock('../../../../common/lib/kibana'); - import React from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { renderHook, cleanup } from '@testing-library/react-hooks'; import { useInstalledIntegrations } from './use_installed_integrations'; -import * as api from '../../../containers/detection_engine/rules/api'; +import { fleetIntegrationsApi } from '../../../../detection_engine/fleet_integrations/api'; import { useToasts } from '../../../../common/lib/kibana'; +jest.mock('../../../../detection_engine/fleet_integrations/api'); +jest.mock('../../../../common/lib/kibana'); + describe('useInstalledIntegrations', () => { beforeEach(() => { jest.clearAllMocks(); @@ -53,7 +53,10 @@ describe('useInstalledIntegrations', () => { ); it('calls the API via fetchInstalledIntegrations', async () => { - const fetchInstalledIntegrations = jest.spyOn(api, 'fetchInstalledIntegrations'); + const fetchInstalledIntegrations = jest.spyOn( + fleetIntegrationsApi, + 'fetchInstalledIntegrations' + ); const { waitForNextUpdate } = render(); @@ -101,7 +104,7 @@ describe('useInstalledIntegrations', () => { // Skipping until we re-enable errors it.skip('handles exceptions from the API', async () => { const exception = new Error('Boom!'); - jest.spyOn(api, 'fetchInstalledIntegrations').mockRejectedValue(exception); + jest.spyOn(fleetIntegrationsApi, 'fetchInstalledIntegrations').mockRejectedValue(exception); const { result, waitForNextUpdate } = render(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.tsx index 734ef6e628214..c94404bb5cdc7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_installed_integrations.tsx @@ -6,8 +6,9 @@ */ import { useQuery } from '@tanstack/react-query'; -import type { InstalledIntegrationArray } from '../../../../../common/detection_engine/schemas/common'; -import { fetchInstalledIntegrations } from '../../../containers/detection_engine/rules/api'; + +import type { InstalledIntegrationArray } from '../../../../../common/detection_engine/fleet_integrations'; +import { fleetIntegrationsApi } from '../../../../detection_engine/fleet_integrations'; // import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; // import * as i18n from './translations'; @@ -28,7 +29,7 @@ export const useInstalledIntegrations = ({ packages }: UseInstalledIntegrationsA }, ], async ({ signal }) => { - const integrations = await fetchInstalledIntegrations({ + const integrations = await fleetIntegrationsApi.fetchInstalledIntegrations({ packages, signal, }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_related_integrations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_related_integrations.ts index 3363abf2fe3c7..19e662746638a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_related_integrations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/use_related_integrations.ts @@ -7,7 +7,7 @@ import { useMemo } from 'react'; -import type { RelatedIntegrationArray } from '../../../../../common/detection_engine/schemas/common'; +import type { RelatedIntegrationArray } from '../../../../../common/detection_engine/rule_schema'; import type { IntegrationDetails } from './integration_details'; import { calculateIntegrationDetails } from './integration_details'; import { useInstalledIntegrations } from './use_installed_integrations'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx index ea5aac0bcae26..177fd2e97e6cd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx @@ -5,6 +5,9 @@ * 2.0. */ +import React, { useCallback, useMemo } from 'react'; +import styled from 'styled-components'; +import { noop } from 'lodash/fp'; import { EuiFormRow, EuiCheckbox, @@ -16,15 +19,14 @@ import { EuiSpacer, EuiRange, } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; -import styled from 'styled-components'; -import { noop } from 'lodash/fp'; -import type { RiskScoreMapping } from '@kbn/securitysolution-io-ts-alerting-types'; -import { FieldComponent } from '@kbn/securitysolution-autocomplete'; + import type { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import * as i18n from './translations'; +import { FieldComponent } from '@kbn/securitysolution-autocomplete'; +import type { RiskScoreMapping } from '@kbn/securitysolution-io-ts-alerting-types'; + import type { AboutStepRiskScore } from '../../../pages/detection_engine/rules/types'; +import * as i18n from './translations'; const NestedContent = styled.div` margin-left: 24px; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 2cbd090e87733..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,74 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RuleActionsOverflow snapshots renders correctly against snapshot 1`] = ` -<Fragment> - <EuiPopover - anchorPosition="leftCenter" - button={ - <EuiToolTip - content="All actions" - delay="regular" - display="inlineBlock" - position="top" - > - <Styled(EuiButtonIcon) - aria-label="All actions" - data-test-subj="rules-details-popover-button-icon" - iconType="boxesHorizontal" - isDisabled={false} - onClick={[Function]} - /> - </EuiToolTip> - } - closePopover={[Function]} - data-test-subj="rules-details-popover" - display="inline-block" - hasArrow={true} - id="ruleActionsOverflow" - isOpen={false} - ownFocus={true} - panelPaddingSize="none" - repositionOnScroll={true} - > - <EuiContextMenuPanel - data-test-subj="rules-details-menu-panel" - items={ - Array [ - <EuiContextMenuItem - data-test-subj="rules-details-duplicate-rule" - disabled={false} - icon="copy" - onClick={[Function]} - > - <EuiToolTip - delay="regular" - display="inlineBlock" - position="left" - > - <React.Fragment> - Duplicate rule - </React.Fragment> - </EuiToolTip> - </EuiContextMenuItem>, - <EuiContextMenuItem - data-test-subj="rules-details-export-rule" - disabled={false} - icon="exportAction" - onClick={[Function]} - > - Export rule - </EuiContextMenuItem>, - <EuiContextMenuItem - data-test-subj="rules-details-delete-rule" - disabled={false} - icon="trash" - onClick={[Function]} - > - Delete rule - </EuiContextMenuItem>, - ] - } - /> - </EuiPopover> -</Fragment> -`; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx index 8b736bad37b92..ce265ffb8382c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.test.tsx @@ -5,17 +5,20 @@ * 2.0. */ -import { shallow, mount } from 'enzyme'; +import { render, fireEvent } from '@testing-library/react'; import React from 'react'; -import { - goToRuleEditPage, - executeRulesBulkAction, - bulkExportRules, -} from '../../../pages/detection_engine/rules/all/actions'; +import { useBulkExport } from '../../../../detection_engine/rule_management/logic/bulk_actions/use_bulk_export'; +import { useExecuteBulkAction } from '../../../../detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action'; + import { RuleActionsOverflow } from '.'; -import { mockRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; +import { mockRule } from '../../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock'; +import { TestProviders } from '../../../../common/mock'; +jest.mock( + '../../../../detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action' +); +jest.mock('../../../../detection_engine/rule_management/logic/bulk_actions/use_bulk_export'); jest.mock('../../../../common/lib/apm/use_start_transaction'); jest.mock('../../../../common/hooks/use_app_toasts'); jest.mock('../../../../common/lib/kibana', () => { @@ -31,12 +34,9 @@ jest.mock('../../../../common/lib/kibana', () => { }), }; }); -jest.mock('../../../pages/detection_engine/rules/all/actions'); - -const executeRulesBulkActionMock = executeRulesBulkAction as jest.Mock; -const bulkExportRulesMock = bulkExportRules as jest.Mock; -const flushPromises = () => new Promise(setImmediate); +const useExecuteBulkActionMock = useExecuteBulkAction as jest.Mock; +const useBulkExportMock = useBulkExport as jest.Mock; describe('RuleActionsOverflow', () => { afterEach(() => { @@ -46,328 +46,189 @@ describe('RuleActionsOverflow', () => { jest.resetAllMocks(); }); - describe('snapshots', () => { - test('renders correctly against snapshot', () => { - const wrapper = shallow( - <RuleActionsOverflow - rule={mockRule('id')} - userHasPermissions - canDuplicateRuleWithActions={true} - /> - ); - expect(wrapper).toMatchSnapshot(); - }); - }); - describe('rules details menu panel', () => { - test('there is at least one item when there is a rule within the rules-details-menu-panel', () => { - const wrapper = mount( + test('menu items rendered when a rule is passed to the component', () => { + const { getByTestId } = render( <RuleActionsOverflow rule={mockRule('id')} userHasPermissions canDuplicateRuleWithActions={true} - /> + />, + { wrapper: TestProviders } ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - const items: unknown[] = wrapper - .find('[data-test-subj="rules-details-menu-panel"]') - .first() - .prop('items'); - - expect(items.length).toBeGreaterThan(0); + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + expect(getByTestId('rules-details-menu-panel')).toHaveTextContent('Duplicate rule'); + expect(getByTestId('rules-details-menu-panel')).toHaveTextContent('Export rule'); + expect(getByTestId('rules-details-menu-panel')).toHaveTextContent('Delete rule'); }); - test('items are empty when there is a null rule within the rules-details-menu-panel', () => { - const wrapper = mount( - <RuleActionsOverflow rule={null} userHasPermissions canDuplicateRuleWithActions={true} /> + test('menu is empty when no rule is passed to the component', () => { + const { getByTestId } = render( + <RuleActionsOverflow rule={null} userHasPermissions canDuplicateRuleWithActions={true} />, + { wrapper: TestProviders } ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - expect( - wrapper.find('[data-test-subj="rules-details-menu-panel"]').first().prop('items') - ).toEqual([]); - }); - - test('items are empty when there is an undefined rule within the rules-details-menu-panel', () => { - const wrapper = mount( - <RuleActionsOverflow rule={null} userHasPermissions canDuplicateRuleWithActions={true} /> - ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - expect( - wrapper.find('[data-test-subj="rules-details-menu-panel"]').first().prop('items') - ).toEqual([]); - }); - - test('it opens the popover when rules-details-popover-button-icon is clicked', () => { - const wrapper = mount( - <RuleActionsOverflow - rule={mockRule('id')} - userHasPermissions - canDuplicateRuleWithActions={true} - /> - ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - expect( - wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') - ).toEqual(true); + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + expect(getByTestId('rules-details-menu-panel')).not.toHaveTextContent(/.+/); }); }); describe('rules details pop over button icon', () => { test('it does not open the popover when rules-details-popover-button-icon is clicked when the user does not have permission', () => { - const wrapper = mount( + const { getByTestId } = render( <RuleActionsOverflow rule={mockRule('id')} userHasPermissions={false} canDuplicateRuleWithActions={true} - /> + />, + { wrapper: TestProviders } ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - expect( - wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') - ).toEqual(false); + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + + expect(getByTestId('rules-details-popover')).not.toHaveTextContent(/.+/); }); }); describe('rules details duplicate rule', () => { - test('it does not open the popover when rules-details-popover-button-icon is clicked and the user does not have permission', () => { - const rule = mockRule('id'); - const wrapper = mount( - <RuleActionsOverflow - rule={rule} - userHasPermissions={false} - canDuplicateRuleWithActions={true} - /> - ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - expect(wrapper.find('[data-test-subj="rules-details-delete-rule"] button').exists()).toEqual( - false - ); - }); - - test('it opens the popover when rules-details-popover-button-icon is clicked', () => { - const wrapper = mount( - <RuleActionsOverflow - rule={mockRule('id')} - userHasPermissions - canDuplicateRuleWithActions={true} - /> - ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - expect( - wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') - ).toEqual(true); - }); - test('it closes the popover when rules-details-duplicate-rule is clicked', () => { - const wrapper = mount( + const { getByTestId } = render( <RuleActionsOverflow rule={mockRule('id')} userHasPermissions canDuplicateRuleWithActions={true} - /> + />, + { wrapper: TestProviders } ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - wrapper.find('[data-test-subj="rules-details-duplicate-rule"] button').simulate('click'); - wrapper.update(); - expect( - wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') - ).toEqual(false); + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + fireEvent.click(getByTestId('rules-details-duplicate-rule')); + + expect(getByTestId('rules-details-popover')).not.toHaveTextContent(/.+/); }); test('it calls duplicate action when rules-details-duplicate-rule is clicked', () => { - const wrapper = mount( + const executeBulkAction = jest.fn(); + useExecuteBulkActionMock.mockReturnValue({ executeBulkAction }); + + const { getByTestId } = render( <RuleActionsOverflow rule={mockRule('id')} userHasPermissions canDuplicateRuleWithActions={true} - /> + />, + { wrapper: TestProviders } ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - wrapper.find('[data-test-subj="rules-details-duplicate-rule"] button').simulate('click'); - wrapper.update(); - expect(executeRulesBulkAction).toHaveBeenCalledWith( + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + fireEvent.click(getByTestId('rules-details-duplicate-rule')); + + expect(executeBulkAction).toHaveBeenCalledWith( expect.objectContaining({ action: 'duplicate' }) ); }); test('it calls duplicate action with the rule and rule.id when rules-details-duplicate-rule is clicked', () => { - const rule = mockRule('id'); - const wrapper = mount( - <RuleActionsOverflow rule={rule} userHasPermissions canDuplicateRuleWithActions={true} /> + const executeBulkAction = jest.fn(); + useExecuteBulkActionMock.mockReturnValue({ executeBulkAction }); + + const { getByTestId } = render( + <RuleActionsOverflow + rule={mockRule('id')} + userHasPermissions + canDuplicateRuleWithActions={true} + />, + { wrapper: TestProviders } ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - wrapper.find('[data-test-subj="rules-details-duplicate-rule"] button').simulate('click'); - wrapper.update(); - expect(executeRulesBulkAction).toHaveBeenCalledWith( + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + fireEvent.click(getByTestId('rules-details-duplicate-rule')); + + expect(executeBulkAction).toHaveBeenCalledWith( expect.objectContaining({ action: 'duplicate', search: { ids: ['id'] } }) ); }); }); - test('it navigates to edit page after the rule is duplicated', async () => { - const rule = mockRule('id'); - const ruleDuplicate = mockRule('newRule'); - executeRulesBulkActionMock.mockImplementation(() => - Promise.resolve({ attributes: { results: { created: [ruleDuplicate] } } }) - ); - const wrapper = mount( - <RuleActionsOverflow rule={rule} userHasPermissions canDuplicateRuleWithActions={true} /> - ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - wrapper.find('[data-test-subj="rules-details-duplicate-rule"] button').simulate('click'); - wrapper.update(); - await flushPromises(); - - expect(executeRulesBulkAction).toHaveBeenCalledWith( - expect.objectContaining({ action: 'duplicate' }) - ); - expect(goToRuleEditPage).toHaveBeenCalledWith(ruleDuplicate.id, expect.anything()); - }); - describe('rules details export rule', () => { test('should call export actions and display toast when export option is clicked', async () => { - bulkExportRulesMock.mockImplementation(() => Promise.resolve({})); - const wrapper = mount( + const bulkExport = jest.fn(); + useBulkExportMock.mockReturnValue({ bulkExport }); + + const { getByTestId } = render( <RuleActionsOverflow rule={mockRule('id')} userHasPermissions canDuplicateRuleWithActions={true} - /> + />, + { wrapper: TestProviders } ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - wrapper.find('[data-test-subj="rules-details-export-rule"] button').simulate('click'); - wrapper.update(); - await flushPromises(); + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + fireEvent.click(getByTestId('rules-details-export-rule')); - expect(bulkExportRulesMock).toHaveBeenCalledWith( - expect.objectContaining({ action: 'export' }) - ); - expect(bulkExportRulesMock).toHaveBeenCalledWith( - expect.not.objectContaining({ onSuccess: expect.any }) - ); - }); - test('it does not open the popover when rules-details-popover-button-icon is clicked and the user does not have permission', () => { - const rule = mockRule('id'); - const wrapper = mount( - <RuleActionsOverflow - rule={rule} - userHasPermissions={false} - canDuplicateRuleWithActions={true} - /> - ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - expect(wrapper.find('[data-test-subj="rules-details-export-rule"] button').exists()).toEqual( - false - ); + expect(bulkExport).toHaveBeenCalled(); }); test('it closes the popover when rules-details-export-rule is clicked', () => { - const wrapper = mount( + const { getByTestId } = render( <RuleActionsOverflow rule={mockRule('id')} userHasPermissions canDuplicateRuleWithActions={true} - /> + />, + { wrapper: TestProviders } ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - wrapper.find('[data-test-subj="rules-details-export-rule"] button').simulate('click'); - wrapper.update(); - expect( - wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') - ).toEqual(false); - }); + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + fireEvent.click(getByTestId('rules-details-export-rule')); - test('it does not close the pop over on rules-details-export-rule when the rule is an immutable rule and the user does a click', () => { - const rule = mockRule('id'); - rule.immutable = true; - const wrapper = mount( - <RuleActionsOverflow rule={rule} userHasPermissions canDuplicateRuleWithActions={true} /> - ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - wrapper.find('[data-test-subj="rules-details-export-rule"] button').simulate('click'); - wrapper.update(); - expect( - wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') - ).toEqual(true); + // Popover is not shown + expect(getByTestId('rules-details-popover')).not.toHaveTextContent(/.+/); }); }); describe('rules details delete rule', () => { - test('it does not open the popover when rules-details-popover-button-icon is clicked and the user does not have permission', () => { - const rule = mockRule('id'); - const wrapper = mount( - <RuleActionsOverflow - rule={rule} - userHasPermissions={false} - canDuplicateRuleWithActions={true} - /> - ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - expect(wrapper.find('[data-test-subj="rules-details-delete-rule"] button').exists()).toEqual( - false - ); - }); - test('it closes the popover when rules-details-delete-rule is clicked', () => { - const wrapper = mount( + const { getByTestId } = render( <RuleActionsOverflow rule={mockRule('id')} userHasPermissions canDuplicateRuleWithActions={true} - /> + />, + { wrapper: TestProviders } ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - wrapper.find('[data-test-subj="rules-details-delete-rule"] button').simulate('click'); - wrapper.update(); - expect( - wrapper.find('[data-test-subj="rules-details-popover"]').first().prop('isOpen') - ).toEqual(false); + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + fireEvent.click(getByTestId('rules-details-delete-rule')); + + // Popover is not shown + expect(getByTestId('rules-details-popover')).not.toHaveTextContent(/.+/); }); test('it calls deleteRulesAction when rules-details-delete-rule is clicked', () => { - const wrapper = mount( + const executeBulkAction = jest.fn(); + useExecuteBulkActionMock.mockReturnValue({ executeBulkAction }); + + const { getByTestId } = render( <RuleActionsOverflow rule={mockRule('id')} userHasPermissions canDuplicateRuleWithActions={true} - /> - ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - wrapper.find('[data-test-subj="rules-details-delete-rule"] button').simulate('click'); - wrapper.update(); - expect(executeRulesBulkAction).toHaveBeenCalledWith( - expect.objectContaining({ action: 'delete' }) + />, + { wrapper: TestProviders } ); + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + fireEvent.click(getByTestId('rules-details-delete-rule')); + + expect(executeBulkAction).toHaveBeenCalledWith(expect.objectContaining({ action: 'delete' })); }); test('it calls deleteRulesAction with the rule.id when rules-details-delete-rule is clicked', () => { + const executeBulkAction = jest.fn(); + useExecuteBulkActionMock.mockReturnValue({ executeBulkAction }); + const rule = mockRule('id'); - const wrapper = mount( - <RuleActionsOverflow rule={rule} userHasPermissions canDuplicateRuleWithActions={true} /> + const { getByTestId } = render( + <RuleActionsOverflow rule={rule} userHasPermissions canDuplicateRuleWithActions={true} />, + { wrapper: TestProviders } ); - wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click'); - wrapper.update(); - wrapper.find('[data-test-subj="rules-details-delete-rule"] button').simulate('click'); - wrapper.update(); - expect(executeRulesBulkAction).toHaveBeenCalledWith( + fireEvent.click(getByTestId('rules-details-popover-button-icon')); + fireEvent.click(getByTestId('rules-details-delete-rule')); + + expect(executeBulkAction).toHaveBeenCalledWith( expect.objectContaining({ action: 'delete', search: { ids: ['id'] } }) ); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx index beb8e8365d74e..c2fc45d53e1d6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx @@ -16,20 +16,23 @@ import { noop } from 'lodash'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { APP_UI_ID, SecurityPageName } from '../../../../../common/constants'; -import { BulkAction } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; import { useKibana } from '../../../../common/lib/kibana'; -import { getToolTipContent } from '../../../../common/utils/privileges'; -import type { Rule } from '../../../containers/detection_engine/rules'; +import { canEditRuleWithActions } from '../../../../common/utils/privileges'; +import type { Rule } from '../../../../detection_engine/rule_management/logic'; +import { + downloadExportedRules, + useBulkExport, +} from '../../../../detection_engine/rule_management/logic/bulk_actions/use_bulk_export'; import { - executeRulesBulkAction, goToRuleEditPage, - bulkExportRules, -} from '../../../pages/detection_engine/rules/all/actions'; + useExecuteBulkAction, +} from '../../../../detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action'; import * as i18nActions from '../../../pages/detection_engine/rules/translations'; import * as i18n from './translations'; @@ -62,6 +65,8 @@ const RuleActionsOverflowComponent = ({ const { navigateToApp } = useKibana().services.application; const toasts = useAppToasts(); const { startTransaction } = useStartTransaction(); + const { executeBulkAction } = useExecuteBulkAction(); + const { bulkExport } = useBulkExport(); const onRuleDeletedCallback = useCallback(() => { navigateToApp(APP_UI_ID, { @@ -82,11 +87,10 @@ const RuleActionsOverflowComponent = ({ onClick={async () => { startTransaction({ name: SINGLE_RULE_ACTIONS.DUPLICATE }); closePopover(); - const result = await executeRulesBulkAction({ + const result = await executeBulkAction({ action: BulkAction.duplicate, onSuccess: noop, search: { ids: [rule.id] }, - toasts, }); const createdRules = result?.attributes.results.created; if (createdRules?.length) { @@ -96,7 +100,11 @@ const RuleActionsOverflowComponent = ({ > <EuiToolTip position="left" - content={getToolTipContent(rule, true, canDuplicateRuleWithActions)} + content={ + !canEditRuleWithActions(rule, canDuplicateRuleWithActions) + ? i18nActions.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES + : undefined + } > <>{i18nActions.DUPLICATE_RULE}</> </EuiToolTip> @@ -109,11 +117,13 @@ const RuleActionsOverflowComponent = ({ onClick={async () => { startTransaction({ name: SINGLE_RULE_ACTIONS.EXPORT }); closePopover(); - await bulkExportRules({ - action: BulkAction.export, - search: { ids: [rule.id] }, - toasts, - }); + const response = await bulkExport({ search: { ids: [rule.id] } }); + if (response) { + await downloadExportedRules({ + response, + toasts, + }); + } }} > {i18nActions.EXPORT_RULE} @@ -126,11 +136,10 @@ const RuleActionsOverflowComponent = ({ onClick={async () => { startTransaction({ name: SINGLE_RULE_ACTIONS.DELETE }); closePopover(); - await executeRulesBulkAction({ + await executeBulkAction({ action: BulkAction.delete, onSuccess: onRuleDeletedCallback, search: { ids: [rule.id] }, - toasts, }); }} > @@ -139,8 +148,10 @@ const RuleActionsOverflowComponent = ({ ] : [], [ + bulkExport, canDuplicateRuleWithActions, closePopover, + executeBulkAction, navigateToApp, onRuleDeletedCallback, rule, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_logs.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_logs.tsx index 8249f60f20869..3d72986a100dd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_logs.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/preview_logs.tsx @@ -7,7 +7,7 @@ import React, { Fragment, useMemo } from 'react'; import { EuiCallOut, EuiText, EuiSpacer, EuiAccordion } from '@elastic/eui'; -import type { RulePreviewLogs } from '../../../../../common/detection_engine/schemas/request'; +import type { RulePreviewLogs } from '../../../../../common/detection_engine/rule_schema'; import * as i18n from './translations'; interface PreviewLogsComponentProps { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_invocation_count.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_invocation_count.ts index fa6cb82980832..ef61f7dd37c13 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_invocation_count.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_invocation_count.ts @@ -8,7 +8,7 @@ import moment from 'moment'; import type { TimeframePreviewOptions } from '../../../pages/detection_engine/rules/types'; -import { getTimeTypeValue } from '../../../pages/detection_engine/rules/create/helpers'; +import { getTimeTypeValue } from '../../../../detection_engine/rule_creation_ui/pages/rule_creation/helpers'; export const usePreviewInvocationCount = ({ timeframeOptions, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx index f6dbe68f07882..a1ccd3472bb75 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_route.tsx @@ -8,8 +8,8 @@ import { useEffect, useState, useCallback } from 'react'; import type { List } from '@kbn/securitysolution-io-ts-list-types'; import { usePreviewRule } from './use_preview_rule'; -import { formatPreviewRule } from '../../../pages/detection_engine/rules/create/helpers'; -import type { RulePreviewLogs } from '../../../../../common/detection_engine/schemas/request'; +import { formatPreviewRule } from '../../../../detection_engine/rule_creation_ui/pages/rule_creation/helpers'; +import type { RulePreviewLogs } from '../../../../../common/detection_engine/rule_schema'; import type { AboutStepRule, DefineStepRule, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_rule.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_rule.ts index 349eaa8c5dd80..17d47008e77bc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_rule.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_preview/use_preview_rule.ts @@ -10,11 +10,11 @@ import { useEffect, useMemo, useState } from 'react'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import type { PreviewResponse, - CreateRulesSchema, -} from '../../../../../common/detection_engine/schemas/request'; + RuleCreateProps, +} from '../../../../../common/detection_engine/rule_schema'; -import { previewRule } from '../../../containers/detection_engine/rules/api'; -import * as i18n from '../../../containers/detection_engine/rules/translations'; +import { previewRule } from '../../../../detection_engine/rule_management/api/api'; +import * as i18n from '../../../../detection_engine/rule_management/logic/translations'; import { transformOutput } from '../../../containers/detection_engine/rules/transforms'; import type { TimeframePreviewOptions } from '../../../pages/detection_engine/rules/types'; import { usePreviewInvocationCount } from './use_preview_invocation_count'; @@ -30,7 +30,7 @@ export const usePreviewRule = ({ }: { timeframeOptions: TimeframePreviewOptions; }) => { - const [rule, setRule] = useState<CreateRulesSchema | null>(null); + const [rule, setRule] = useState<RuleCreateProps | null>(null); const [response, setResponse] = useState<PreviewResponse>(emptyPreviewRule); const [isLoading, setIsLoading] = useState(false); const { addError } = useAppToasts(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.test.tsx index 6580d8955ae2f..9989cd76b09c9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.test.tsx @@ -9,18 +9,20 @@ import { mount } from 'enzyme'; import React from 'react'; import { waitFor } from '@testing-library/react'; -import { performBulkAction } from '../../../containers/detection_engine/rules'; +import { performBulkAction } from '../../../../detection_engine/rule_management/api/api'; import { RuleSwitchComponent } from '.'; -import { getRulesSchemaMock } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; -import { useRulesTableContextOptional } from '../../../pages/detection_engine/rules/all/rules_table/rules_table_context'; -import { useRulesTableContextMock } from '../../../pages/detection_engine/rules/all/rules_table/__mocks__/rules_table_context'; +import { getRulesSchemaMock } from '../../../../../common/detection_engine/rule_schema/mocks'; +import { useRulesTableContextOptional } from '../../../../detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context'; +import { useRulesTableContextMock } from '../../../../detection_engine/rule_management_ui/components/rules_table/rules_table/__mocks__/rules_table_context'; import { TestProviders } from '../../../../common/mock'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; jest.mock('../../../../common/hooks/use_app_toasts'); -jest.mock('../../../containers/detection_engine/rules'); -jest.mock('../../../pages/detection_engine/rules/all/rules_table/rules_table_context'); +jest.mock('../../../../detection_engine/rule_management/api/api'); +jest.mock( + '../../../../detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context' +); jest.mock('../../../../common/lib/apm/use_start_transaction'); const useAppToastsValueMock = useAppToastsMock.create(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx index 6c924a68da4a1..bcd0e74d6ef9f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_switch/index.tsx @@ -10,13 +10,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSwitch } from '@elasti import { noop } from 'lodash'; import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; -import { BulkAction } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { BulkAction } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { SINGLE_RULE_ACTIONS } from '../../../../common/lib/apm/user_actions'; import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; -import { useUpdateRulesCache } from '../../../containers/detection_engine/rules/use_find_rules_query'; -import { executeRulesBulkAction } from '../../../pages/detection_engine/rules/all/actions'; -import { useRulesTableContextOptional } from '../../../pages/detection_engine/rules/all/rules_table/rules_table_context'; +import { useExecuteBulkAction } from '../../../../detection_engine/rule_management/logic/bulk_actions/use_execute_bulk_action'; +import { useRulesTableContextOptional } from '../../../../detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context'; const StaticSwitch = styled(EuiSwitch)` .euiSwitch__thumb, @@ -47,9 +45,8 @@ export const RuleSwitchComponent = ({ }: RuleSwitchProps) => { const [myIsLoading, setMyIsLoading] = useState(false); const rulesTableContext = useRulesTableContextOptional(); - const updateRulesCache = useUpdateRulesCache(); - const toasts = useAppToasts(); const { startTransaction } = useStartTransaction(); + const { executeBulkAction } = useExecuteBulkAction(); const onRuleStateChange = useCallback( async (event: EuiSwitchEvent) => { @@ -57,9 +54,8 @@ export const RuleSwitchComponent = ({ startTransaction({ name: enabled ? SINGLE_RULE_ACTIONS.DISABLE : SINGLE_RULE_ACTIONS.ENABLE, }); - const bulkActionResponse = await executeRulesBulkAction({ + const bulkActionResponse = await executeBulkAction({ setLoadingRules: rulesTableContext?.actions.setLoadingRules, - toasts, onSuccess: rulesTableContext ? undefined : noop, action: event.target.checked ? BulkAction.enable : BulkAction.disable, search: { ids: [id] }, @@ -67,12 +63,11 @@ export const RuleSwitchComponent = ({ }); if (bulkActionResponse?.attributes.results.updated.length) { // The rule was successfully updated - updateRulesCache(bulkActionResponse.attributes.results.updated); onChange?.(bulkActionResponse.attributes.results.updated[0].enabled); } setMyIsLoading(false); }, - [enabled, id, onChange, rulesTableContext, startTransaction, toasts, updateRulesCache] + [enabled, executeBulkAction, id, onChange, rulesTableContext, startTransaction] ); const showLoader = useMemo((): boolean => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx index 01569edd6907d..52e96c088c200 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx @@ -18,7 +18,6 @@ import { isNewTermsRule, } from '../../../../../common/detection_engine/utils'; import type { FieldHook } from '../../../../shared_imports'; -import { useKibana } from '../../../../common/lib/kibana'; import * as i18n from './translations'; import { MlCardDescription } from './ml_card_description'; @@ -50,9 +49,6 @@ export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({ const setThreshold = useCallback(() => setType('threshold'), [setType]); const setThreatMatch = useCallback(() => setType('threat_match'), [setType]); const setNewTerms = useCallback(() => setType('new_terms'), [setType]); - const licensingUrl = useKibana().services.application.getUrlForApp('kibana', { - path: '#/management/stack/license_management', - }); const eqlSelectableConfig = useMemo( () => ({ @@ -130,12 +126,7 @@ export const SelectRuleType: React.FC<SelectRuleTypeProps> = ({ data-test-subj="machineLearningRuleType" title={i18n.ML_TYPE_TITLE} titleSize="xs" - description={ - <MlCardDescription - subscriptionUrl={licensingUrl} - hasValidLicense={hasValidLicense} - /> - } + description={<MlCardDescription hasValidLicense={hasValidLicense} />} icon={<EuiIcon size="l" type="machineLearningApp" />} isDisabled={mlSelectableConfig.isDisabled && !mlSelectableConfig.isSelected} selectable={mlSelectableConfig} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/ml_card_description.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/ml_card_description.tsx index 51ad3e1d83e96..f017bc2d52a8d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/ml_card_description.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/ml_card_description.tsx @@ -13,7 +13,6 @@ import React from 'react'; import { ML_TYPE_DESCRIPTION } from './translations'; interface MlCardDescriptionProps { - subscriptionUrl: string; hasValidLicense?: boolean; } @@ -22,7 +21,6 @@ const SmallText = styled.span` `; const MlCardDescriptionComponent: React.FC<MlCardDescriptionProps> = ({ - subscriptionUrl, hasValidLicense = false, }) => ( <SmallText> @@ -34,7 +32,7 @@ const MlCardDescriptionComponent: React.FC<MlCardDescriptionProps> = ({ defaultMessage="Access to ML requires a {subscriptionsLink}." values={{ subscriptionsLink: ( - <EuiLink href={subscriptionUrl} target="_blank"> + <EuiLink href="https://www.elastic.co/subscriptions" target="_blank"> <FormattedMessage id="xpack.securitySolution.components.stepDefineRule.ruleTypeField.subscriptionsLink" defaultMessage="Platinum subscription" diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx index af746d158e2a7..9f3e6c927de83 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/severity_badge/index.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import { upperFirst } from 'lodash/fp'; import React from 'react'; +import { upperFirst } from 'lodash/fp'; import { euiLightVars } from '@kbn/ui-theme'; import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; + import { HealthTruncateText } from '../../../../common/components/health_truncate_text'; const { euiColorVis0, euiColorVis5, euiColorVis7, euiColorVis9 } = euiLightVars; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx index 961620d1521c4..aeefb4091307e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx @@ -19,22 +19,23 @@ import { import { noop } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; + +import type { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; +import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { + FieldComponent, + AutocompleteFieldMatchComponent, +} from '@kbn/securitysolution-autocomplete'; import type { Severity, SeverityMapping, SeverityMappingItem, } from '@kbn/securitysolution-io-ts-alerting-types'; -import { - FieldComponent, - AutocompleteFieldMatchComponent, -} from '@kbn/securitysolution-autocomplete'; -import type { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; -import type { FieldHook } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import * as i18n from './translations'; import type { SeverityOptionItem } from '../step_about_rule/data'; import type { AboutStepSeverity } from '../../../pages/detection_engine/rules/types'; import { useKibana } from '../../../../common/lib/kibana'; +import * as i18n from './translations'; const NestedContent = styled.div` margin-left: 24px; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/data.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/data.tsx index d0d471e3a727b..bab334b1c7f1f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/data.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/data.tsx @@ -5,11 +5,10 @@ * 2.0. */ +import React from 'react'; import styled from 'styled-components'; import { EuiHealth } from '@elastic/eui'; import { euiLightVars } from '@kbn/ui-theme'; -import React from 'react'; - import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import * as I18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx index 4c3f635069c0f..5c5554b72ad8a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx @@ -13,7 +13,7 @@ import { stubIndexPattern } from '@kbn/data-plugin/common/stubs'; import { StepAboutRule } from '.'; import { useFetchIndex } from '../../../../common/containers/source'; import { useGetInstalledJob } from '../../../../common/components/ml/hooks/use_get_jobs'; -import { mockAboutStepRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; +import { mockAboutStepRule } from '../../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock'; import { StepRuleDescription } from '../description_step'; import { stepAboutDefaultValue } from './default_value'; import type { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx index 34303c00e5153..22c9670e3bed8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx @@ -44,7 +44,7 @@ import { AutocompleteField } from '../autocomplete_field'; import { useFetchIndex } from '../../../../common/containers/source'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; import { useKibana } from '../../../../common/lib/kibana'; -import { useRuleIndices } from '../../../containers/detection_engine/rules/use_rule_indices'; +import { useRuleIndices } from '../../../../detection_engine/rule_management/logic/use_rule_indices'; const CommonUseField = getUseField({ component: Field }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.test.tsx index cafbc1f173f0b..ae46ccef9e859 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.test.tsx @@ -11,7 +11,7 @@ import { EuiProgress, EuiButtonGroup } from '@elastic/eui'; import { ThemeProvider } from 'styled-components'; import { StepAboutRuleToggleDetails } from '.'; -import { mockAboutStepRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock'; +import { mockAboutStepRule } from '../../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock'; import { HeaderSection } from '../../../../common/components/header_section'; import { StepAboutRule } from '../step_about_rule'; import type { AboutStepRule } from '../../../pages/detection_engine/rules/types'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 1dd2a2d1de798..dc926fd4f74e2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -36,7 +36,7 @@ import type { EqlOptionsSelected, FieldsEqlOptions } from '../../../../../common import { filterRuleFieldsForType, getStepDataDataSource, -} from '../../../pages/detection_engine/rules/create/helpers'; +} from '../../../../detection_engine/rule_creation_ui/pages/rule_creation/helpers'; import type { DefineStepRule, RuleStepProps } from '../../../pages/detection_engine/rules/types'; import { RuleStep, DataSourceType } from '../../../pages/detection_engine/rules/types'; import { StepRuleDescription } from '../description_step'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts index 2b280786e2905..060f36f5bb5c8 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/exceptions/get_es_query_filter.ts @@ -13,14 +13,17 @@ import { buildEsQuery } from '@kbn/es-query'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { KibanaServices } from '../../../../common/lib/kibana'; -import type { Query, Index } from '../../../../../common/detection_engine/schemas/common'; +import type { + IndexPatternArray, + RuleQuery, +} from '../../../../../common/detection_engine/rule_schema'; import type { ESBoolQuery } from '../../../../../common/typed_json'; export const getEsQueryFilter = async ( - query: Query, + query: RuleQuery, language: Language, filters: unknown, - index: Index, + index: IndexPatternArray, lists: ExceptionListItemSchema[], excludeExceptions: boolean = true ): Promise<ESBoolQuery> => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/transforms.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/transforms.ts index ab4d1eedbd2ae..a78191913562a 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/transforms.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/transforms.ts @@ -8,10 +8,10 @@ import { flow } from 'fp-ts/lib/function'; import { addIdToItem, removeIdFromItem } from '@kbn/securitysolution-utils'; import type { - CreateRulesSchema, - UpdateRulesSchema, -} from '../../../../../common/detection_engine/schemas/request'; -import type { Rule } from './types'; + RuleCreateProps, + RuleUpdateProps, +} from '../../../../../common/detection_engine/rule_schema'; +import type { Rule } from '../../../../detection_engine/rule_management/logic/types'; // These are a collection of transforms that are UI specific and useful for UI concerns // that are inserted between the API and the actual user interface. In some ways these @@ -31,8 +31,8 @@ import type { Rule } from './types'; * @returns The rule transformed from the output */ export const transformOutput = ( - rule: CreateRulesSchema | UpdateRulesSchema -): CreateRulesSchema | UpdateRulesSchema => flow(removeIdFromThreatMatchArray)(rule); + rule: RuleCreateProps | RuleUpdateProps +): RuleCreateProps | RuleUpdateProps => flow(removeIdFromThreatMatchArray)(rule); /** * Transforms the output of rules to compensate for technical debt or UI concerns such as @@ -84,8 +84,8 @@ export const addIdToThreatMatchArray = (rule: Rule): Rule => { * @returns rule The rule but with id removed from the threat array and entries */ export const removeIdFromThreatMatchArray = ( - rule: CreateRulesSchema | UpdateRulesSchema -): CreateRulesSchema | UpdateRulesSchema => { + rule: RuleCreateProps | RuleUpdateProps +): RuleCreateProps | RuleUpdateProps => { if (rule.type === 'threat_match' && rule.threat_mapping != null) { const threatMapWithoutId = rule.threat_mapping.map((mapping) => { const newEntries = mapping.entries.map((entry) => removeIdFromItem(entry)); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/translations.ts deleted file mode 100644 index c1161db6db26f..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/translations.ts +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const RULE_AND_TIMELINE_FETCH_FAILURE = i18n.translate( - 'xpack.securitySolution.containers.detectionEngine.rulesAndTimelines', - { - defaultMessage: 'Failed to fetch Rules and Timelines', - } -); - -export const RULE_ADD_FAILURE = i18n.translate( - 'xpack.securitySolution.containers.detectionEngine.addRuleFailDescription', - { - defaultMessage: 'Failed to add Rule', - } -); - -export const RULE_AND_TIMELINE_PREPACKAGED_FAILURE = i18n.translate( - 'xpack.securitySolution.containers.detectionEngine.createPrePackagedRuleAndTimelineFailDescription', - { - defaultMessage: 'Failed to installed pre-packaged rules and timelines from elastic', - } -); - -export const RULE_AND_TIMELINE_PREPACKAGED_SUCCESS = i18n.translate( - 'xpack.securitySolution.containers.detectionEngine.createPrePackagedRuleAndTimelineSuccesDescription', - { - defaultMessage: 'Installed pre-packaged rules and timeline templates from elastic', - } -); - -export const RULE_PREPACKAGED_SUCCESS = i18n.translate( - 'xpack.securitySolution.containers.detectionEngine.createPrePackagedRuleSuccesDescription', - { - defaultMessage: 'Installed pre-packaged rules from elastic', - } -); - -export const TIMELINE_PREPACKAGED_SUCCESS = i18n.translate( - 'xpack.securitySolution.containers.detectionEngine.createPrePackagedTimelineSuccesDescription', - { - defaultMessage: 'Installed pre-packaged timeline templates from elastic', - } -); - -export const TAG_FETCH_FAILURE = i18n.translate( - 'xpack.securitySolution.containers.detectionEngine.tagFetchFailDescription', - { - defaultMessage: 'Failed to fetch Tags', - } -); - -export const LOAD_PREPACKAGED_RULES = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.loadPrePackagedRulesButton', - { - defaultMessage: 'Load Elastic prebuilt rules', - } -); - -export const LOAD_PREPACKAGED_TIMELINE_TEMPLATES = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.loadPrePackagedTimelineTemplatesButton', - { - defaultMessage: 'Load Elastic prebuilt timeline templates', - } -); - -export const LOAD_PREPACKAGED_RULES_AND_TEMPLATES = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.loadPrePackagedRulesAndTemplatesButton', - { - defaultMessage: 'Load Elastic prebuilt rules and timeline templates', - } -); - -export const RELOAD_MISSING_PREPACKAGED_RULES = (missingRules: number) => - i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesButton', - { - values: { missingRules }, - defaultMessage: - 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ', - } - ); - -export const RELOAD_MISSING_PREPACKAGED_TIMELINES = (missingTimelines: number) => - i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedTimelinesButton', - { - values: { missingTimelines }, - defaultMessage: - 'Install {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ', - } - ); - -export const RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES = ( - missingRules: number, - missingTimelines: number -) => - i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesAndTimelinesButton', - { - values: { missingRules, missingTimelines }, - defaultMessage: - 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} and {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ', - } - ); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.test.tsx deleted file mode 100644 index 71d8aacff4280..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.test.tsx +++ /dev/null @@ -1,68 +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 { renderHook, act } from '@testing-library/react-hooks'; - -import type { ReturnCreateRule } from './use_create_rule'; -import { useCreateRule } from './use_create_rule'; -import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; -import { getRulesSchemaMock } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; -import { TestProviders } from '../../../../common/mock'; - -jest.mock('./api'); -jest.mock('../../../../common/hooks/use_app_toasts'); - -describe('useCreateRule', () => { - (useAppToasts as jest.Mock).mockReturnValue(useAppToastsMock.create()); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('init', async () => { - const { result } = renderHook<unknown, ReturnCreateRule>(() => useCreateRule(), { - wrapper: TestProviders, - }); - - expect(result.current).toEqual([{ isLoading: false, ruleId: null }, result.current[1]]); - }); - - test('saving rule with isLoading === true', async () => { - await act(async () => { - const { result, rerender, waitForNextUpdate } = renderHook<void, ReturnCreateRule>( - () => useCreateRule(), - { - wrapper: TestProviders, - } - ); - await waitForNextUpdate(); - result.current[1](getCreateRulesSchemaMock()); - rerender(); - expect(result.current).toEqual([{ isLoading: true, ruleId: null }, result.current[1]]); - }); - }); - - test('updates ruleId after the rule has been saved', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<void, ReturnCreateRule>( - () => useCreateRule(), - { - wrapper: TestProviders, - } - ); - await waitForNextUpdate(); - result.current[1](getCreateRulesSchemaMock()); - await waitForNextUpdate(); - expect(result.current).toEqual([ - { isLoading: false, ruleId: getRulesSchemaMock().id }, - result.current[1], - ]); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.tsx deleted file mode 100644 index f2b76c306dc5f..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.tsx +++ /dev/null @@ -1,71 +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 { Dispatch } from 'react'; -import { useEffect, useState } from 'react'; - -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { CreateRulesSchema } from '../../../../../common/detection_engine/schemas/request'; - -import { createRule } from './api'; -import * as i18n from './translations'; -import { transformOutput } from './transforms'; -import { useInvalidateRules } from './use_find_rules_query'; -import { useInvalidatePrePackagedRulesStatus } from './use_pre_packaged_rules_status'; - -interface CreateRuleReturn { - isLoading: boolean; - ruleId: string | null; -} - -export type ReturnCreateRule = [CreateRuleReturn, Dispatch<CreateRulesSchema | null>]; - -export const useCreateRule = (): ReturnCreateRule => { - const [rule, setRule] = useState<CreateRulesSchema | null>(null); - const [ruleId, setRuleId] = useState<string | null>(null); - const [isLoading, setIsLoading] = useState(false); - const { addError } = useAppToasts(); - const invalidateRules = useInvalidateRules(); - const invalidatePrePackagedRulesStatus = useInvalidatePrePackagedRulesStatus(); - - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - setRuleId(null); - const saveRule = async () => { - if (rule != null) { - try { - setIsLoading(true); - const createRuleResponse = await createRule({ - rule: transformOutput(rule), - signal: abortCtrl.signal, - }); - invalidateRules(); - invalidatePrePackagedRulesStatus(); - if (isSubscribed) { - setRuleId(createRuleResponse.id); - } - } catch (error) { - if (isSubscribed) { - addError(error, { title: i18n.RULE_ADD_FAILURE }); - } - } - if (isSubscribed) { - setIsLoading(false); - } - } - }; - - saveRule(); - return () => { - isSubscribed = false; - abortCtrl.abort(); - }; - }, [rule, addError, invalidateRules, invalidatePrePackagedRulesStatus]); - - return [{ isLoading, ruleId }, setRule]; -}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx deleted file mode 100644 index 5cddbeef63028..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx +++ /dev/null @@ -1,540 +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 { act, renderHook } from '@testing-library/react-hooks'; -import { shallow } from 'enzyme'; -import type { ReactElement } from 'react'; -import { useToasts } from '../../../../common/lib/kibana'; -import { TestProviders } from '../../../../common/mock'; -import * as api from './api'; -import * as i18n from './translations'; -import type { ReturnPrePackagedRulesAndTimelines } from './use_pre_packaged_rules'; -import { usePrePackagedRules } from './use_pre_packaged_rules'; - -jest.mock('../../../../common/lib/kibana', () => ({ - useKibana: jest.fn(), - useToasts: jest.fn().mockReturnValue({ - addError: jest.fn(), - addSuccess: jest.fn(), - addWarning: jest.fn(), - remove: jest.fn(), - }), -})); - -jest.mock('./api', () => ({ - getPrePackagedRulesStatus: jest.fn(), - createPrepackagedRules: jest.fn(), -})); - -describe('usePrePackagedRules', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('initial state', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: null, - hasIndexWrite: null, - isAuthenticated: null, - hasEncryptionKey: null, - isSignalIndexExists: null, - }), - { wrapper: TestProviders } - ); - - await waitForNextUpdate(); - - expect(result.current).toEqual({ - getLoadPrebuiltRulesAndTemplatesButton: expect.any(Function), - getReloadPrebuiltRulesAndTemplatesButton: expect.any(Function), - createPrePackagedRules: expect.any(Function), - loading: true, - loadingCreatePrePackagedRules: false, - }); - }); - }); - - test('fetch getPrePackagedRulesStatus', async () => { - (api.getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ - rules_custom_installed: 33, - rules_installed: 12, - rules_not_installed: 0, - rules_not_updated: 0, - timelines_installed: 0, - timelines_not_installed: 0, - timelines_not_updated: 0, - }); - (api.createPrepackagedRules as jest.Mock).mockResolvedValue({ - rules_installed: 0, - rules_updated: 0, - timelines_installed: 0, - timelines_updated: 0, - }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: null, - hasIndexWrite: null, - isAuthenticated: null, - hasEncryptionKey: null, - isSignalIndexExists: null, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - - expect(result.current).toEqual({ - getLoadPrebuiltRulesAndTemplatesButton: - result.current.getLoadPrebuiltRulesAndTemplatesButton, - getReloadPrebuiltRulesAndTemplatesButton: - result.current.getReloadPrebuiltRulesAndTemplatesButton, - createPrePackagedRules: result.current.createPrePackagedRules, - loading: false, - loadingCreatePrePackagedRules: false, - rulesCustomInstalled: 33, - rulesInstalled: 12, - rulesNotInstalled: 0, - rulesNotUpdated: 0, - timelinesInstalled: 0, - timelinesNotInstalled: 0, - timelinesNotUpdated: 0, - }); - }); - }); - - test('happy path to createPrePackagedRules', async () => { - (api.getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ - rules_custom_installed: 33, - rules_installed: 12, - rules_not_installed: 0, - rules_not_updated: 0, - timelines_installed: 0, - timelines_not_installed: 0, - timelines_not_updated: 0, - }); - (api.createPrepackagedRules as jest.Mock).mockResolvedValue({ - rules_installed: 0, - rules_updated: 0, - timelines_installed: 0, - timelines_updated: 0, - }); - - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: true, - isAuthenticated: true, - hasEncryptionKey: true, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - result.current.createPrePackagedRules(); - await waitForNextUpdate(); - expect(api.createPrepackagedRules).toHaveBeenCalled(); - await waitForNextUpdate(); - expect(result.current).toEqual({ - getLoadPrebuiltRulesAndTemplatesButton: - result.current.getLoadPrebuiltRulesAndTemplatesButton, - getReloadPrebuiltRulesAndTemplatesButton: - result.current.getReloadPrebuiltRulesAndTemplatesButton, - createPrePackagedRules: result.current.createPrePackagedRules, - loading: false, - loadingCreatePrePackagedRules: false, - rulesCustomInstalled: 33, - rulesInstalled: 12, - rulesNotInstalled: 0, - rulesNotUpdated: 0, - timelinesInstalled: 0, - timelinesNotInstalled: 0, - timelinesNotUpdated: 0, - }); - }); - }); - - test('getLoadPrebuiltRulesAndTemplatesButton - LOAD_PREPACKAGED_RULES', async () => { - (api.getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ - rules_custom_installed: 0, - rules_installed: 0, - rules_not_installed: 1, - rules_not_updated: 0, - timelines_installed: 0, - timelines_not_installed: 0, - timelines_not_updated: 0, - }); - (api.createPrepackagedRules as jest.Mock).mockResolvedValue({ - rules_installed: 0, - rules_updated: 0, - timelines_installed: 0, - timelines_updated: 0, - }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: true, - isAuthenticated: true, - hasEncryptionKey: true, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - - const button = result.current.getLoadPrebuiltRulesAndTemplatesButton({ - isDisabled: false, - onClick: jest.fn(), - 'data-test-subj': 'button', - }); - const wrapper = shallow(button as ReactElement); - expect(wrapper.find('[data-test-subj="button"]').text()).toEqual(i18n.LOAD_PREPACKAGED_RULES); - }); - }); - - test('getLoadPrebuiltRulesAndTemplatesButton - LOAD_PREPACKAGED_TIMELINE_TEMPLATES', async () => { - (api.getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ - rules_custom_installed: 0, - rules_installed: 0, - rules_not_installed: 0, - rules_not_updated: 0, - timelines_installed: 0, - timelines_not_installed: 1, - timelines_not_updated: 0, - }); - (api.createPrepackagedRules as jest.Mock).mockResolvedValue({ - rules_installed: 0, - rules_updated: 0, - timelines_installed: 0, - timelines_updated: 0, - }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: true, - isAuthenticated: true, - hasEncryptionKey: true, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - - const button = result.current.getLoadPrebuiltRulesAndTemplatesButton({ - isDisabled: false, - onClick: jest.fn(), - 'data-test-subj': 'button', - }); - const wrapper = shallow(button as ReactElement); - expect(wrapper.find('[data-test-subj="button"]').text()).toEqual( - i18n.LOAD_PREPACKAGED_TIMELINE_TEMPLATES - ); - }); - }); - - test('getLoadPrebuiltRulesAndTemplatesButton - LOAD_PREPACKAGED_RULES_AND_TEMPLATES', async () => { - (api.getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ - rules_custom_installed: 0, - rules_installed: 0, - rules_not_installed: 1, - rules_not_updated: 0, - timelines_installed: 0, - timelines_not_installed: 1, - timelines_not_updated: 0, - }); - (api.createPrepackagedRules as jest.Mock).mockResolvedValue({ - rules_installed: 0, - rules_updated: 0, - timelines_installed: 0, - timelines_updated: 0, - }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: true, - isAuthenticated: true, - hasEncryptionKey: true, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - - const button = result.current.getLoadPrebuiltRulesAndTemplatesButton({ - isDisabled: false, - onClick: jest.fn(), - 'data-test-subj': 'button', - }); - const wrapper = shallow(button as ReactElement); - expect(wrapper.find('[data-test-subj="button"]').text()).toEqual( - i18n.LOAD_PREPACKAGED_RULES_AND_TEMPLATES - ); - }); - }); - - test('getReloadPrebuiltRulesAndTemplatesButton - missing rules and templates', async () => { - (api.getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ - rules_custom_installed: 0, - rules_installed: 1, - rules_not_installed: 1, - rules_not_updated: 0, - timelines_installed: 0, - timelines_not_installed: 1, - timelines_not_updated: 0, - }); - (api.createPrepackagedRules as jest.Mock).mockResolvedValue({ - rules_installed: 0, - rules_updated: 0, - timelines_installed: 0, - timelines_updated: 0, - }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: true, - isAuthenticated: true, - hasEncryptionKey: true, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - - const button = result.current.getReloadPrebuiltRulesAndTemplatesButton({ - isDisabled: false, - onClick: jest.fn(), - }); - const wrapper = shallow(button as ReactElement); - expect(wrapper.find('[data-test-subj="reloadPrebuiltRulesBtn"]').text()).toEqual( - 'Install 1 Elastic prebuilt rule and 1 Elastic prebuilt timeline ' - ); - }); - }); - - test('getReloadPrebuiltRulesAndTemplatesButton - missing rules', async () => { - (api.getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ - rules_custom_installed: 0, - rules_installed: 1, - rules_not_installed: 1, - rules_not_updated: 0, - timelines_installed: 0, - timelines_not_installed: 0, - timelines_not_updated: 0, - }); - (api.createPrepackagedRules as jest.Mock).mockResolvedValue({ - rules_installed: 0, - rules_updated: 0, - timelines_installed: 0, - timelines_updated: 0, - }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: true, - isAuthenticated: true, - hasEncryptionKey: true, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - - const button = result.current.getReloadPrebuiltRulesAndTemplatesButton({ - isDisabled: false, - onClick: jest.fn(), - }); - const wrapper = shallow(button as ReactElement); - expect(wrapper.find('[data-test-subj="reloadPrebuiltRulesBtn"]').text()).toEqual( - 'Install 1 Elastic prebuilt rule ' - ); - }); - }); - - test('getReloadPrebuiltRulesAndTemplatesButton - missing templates', async () => { - (api.getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({ - rules_custom_installed: 0, - rules_installed: 1, - rules_not_installed: 0, - rules_not_updated: 0, - timelines_installed: 1, - timelines_not_installed: 1, - timelines_not_updated: 0, - }); - (api.createPrepackagedRules as jest.Mock).mockResolvedValue({ - rules_installed: 0, - rules_updated: 0, - timelines_installed: 0, - timelines_updated: 0, - }); - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: true, - isAuthenticated: true, - hasEncryptionKey: true, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - - const button = result.current.getReloadPrebuiltRulesAndTemplatesButton({ - isDisabled: false, - onClick: jest.fn(), - }); - const wrapper = shallow(button as ReactElement); - expect(wrapper.find('[data-test-subj="reloadPrebuiltRulesBtn"]').text()).toEqual( - 'Install 1 Elastic prebuilt timeline ' - ); - }); - }); - - test('unhappy path to createPrePackagedRules', async () => { - (api.createPrepackagedRules as jest.Mock).mockRejectedValue(new Error('Something went wrong')); - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: true, - isAuthenticated: true, - hasEncryptionKey: true, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - result.current.createPrePackagedRules(); - await waitForNextUpdate(); - expect(api.createPrepackagedRules).toHaveBeenCalled(); - expect(useToasts().addError).toHaveBeenCalled(); - }); - }); - - test('can NOT createPrePackagedRules because canUserCrud === false', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: false, - hasIndexWrite: true, - isAuthenticated: true, - hasEncryptionKey: true, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - result.current.createPrePackagedRules(); - await waitForNextUpdate(); - expect(api.createPrepackagedRules).not.toHaveBeenCalled(); - }); - }); - - test('can NOT createPrePackagedRules because hasIndexWrite === false', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: false, - isAuthenticated: true, - hasEncryptionKey: true, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - result.current.createPrePackagedRules(); - await waitForNextUpdate(); - expect(api.createPrepackagedRules).not.toHaveBeenCalled(); - }); - }); - - test('can NOT createPrePackagedRules because isAuthenticated === false', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: true, - isAuthenticated: false, - hasEncryptionKey: true, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - result.current.createPrePackagedRules(); - await waitForNextUpdate(); - expect(api.createPrepackagedRules).not.toHaveBeenCalled(); - }); - }); - - test('can NOT createPrePackagedRules because hasEncryptionKey === false', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: true, - isAuthenticated: true, - hasEncryptionKey: false, - isSignalIndexExists: true, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - result.current.createPrePackagedRules(); - await waitForNextUpdate(); - expect(api.createPrepackagedRules).not.toHaveBeenCalled(); - }); - }); - - test('can NOT createPrePackagedRules because isSignalIndexExists === false', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<unknown, ReturnPrePackagedRulesAndTimelines>( - () => - usePrePackagedRules({ - canUserCRUD: true, - hasIndexWrite: true, - isAuthenticated: true, - hasEncryptionKey: true, - isSignalIndexExists: false, - }), - { wrapper: TestProviders } - ); - await waitForNextUpdate(); - result.current.createPrePackagedRules(); - await waitForNextUpdate(); - expect(api.createPrepackagedRules).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx deleted file mode 100644 index 88bfe5c618c01..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx +++ /dev/null @@ -1,200 +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 } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; -import { - getPrePackagedRuleStatus, - getPrePackagedTimelineStatus, -} from '../../../pages/detection_engine/rules/helpers'; -import * as i18n from './translations'; -import { useInstallPrePackagedRules } from './use_install_pre_packaged_rules'; -import type { PrePackagedRulesStatusResponse } from './use_pre_packaged_rules_status'; -import { usePrePackagedRulesStatus } from './use_pre_packaged_rules_status'; - -type GetLoadPrebuiltRulesAndTemplatesButton = (args: { - isDisabled: boolean; - onClick: () => void; - fill?: boolean; - 'data-test-subj'?: string; -}) => React.ReactNode | null; - -type GetReloadPrebuiltRulesAndTemplatesButton = ({ - isDisabled, - onClick, - fill, -}: { - isDisabled: boolean; - onClick: () => void; - fill?: boolean; -}) => React.ReactNode | null; - -interface ReturnPrePackagedRules { - createPrePackagedRules: () => void; - loading: boolean; - loadingCreatePrePackagedRules: boolean; - getLoadPrebuiltRulesAndTemplatesButton: GetLoadPrebuiltRulesAndTemplatesButton; - getReloadPrebuiltRulesAndTemplatesButton: GetReloadPrebuiltRulesAndTemplatesButton; -} - -export type ReturnPrePackagedRulesAndTimelines = ReturnPrePackagedRules & - Partial<PrePackagedRulesStatusResponse>; - -interface UsePrePackagedRuleProps { - canUserCRUD: boolean | null; - hasIndexWrite: boolean | null; - isAuthenticated: boolean | null; - hasEncryptionKey: boolean | null; - isSignalIndexExists: boolean | null; -} - -/** - * Hook for using to get status about pre-packaged Rules from the Detection Engine API - * - * @param hasIndexWrite boolean - * @param isAuthenticated boolean - * @param hasEncryptionKey boolean - * @param isSignalIndexExists boolean - * - */ -export const usePrePackagedRules = ({ - canUserCRUD, - hasIndexWrite, - isAuthenticated, - hasEncryptionKey, - isSignalIndexExists, -}: UsePrePackagedRuleProps): ReturnPrePackagedRulesAndTimelines => { - const { data: prePackagedRulesStatus, isFetching } = usePrePackagedRulesStatus(); - const { mutate: installPrePackagedRules, isLoading: loadingCreatePrePackagedRules } = - useInstallPrePackagedRules(); - - const createPrePackagedRules = useCallback(() => { - if ( - canUserCRUD && - hasIndexWrite && - isAuthenticated && - hasEncryptionKey && - isSignalIndexExists - ) { - installPrePackagedRules(); - } - }, [ - canUserCRUD, - hasEncryptionKey, - hasIndexWrite, - installPrePackagedRules, - isAuthenticated, - isSignalIndexExists, - ]); - - const prePackagedAssetsStatus = useMemo( - () => - getPrePackagedRuleStatus( - prePackagedRulesStatus?.rulesInstalled, - prePackagedRulesStatus?.rulesNotInstalled, - prePackagedRulesStatus?.rulesNotUpdated - ), - [ - prePackagedRulesStatus?.rulesInstalled, - prePackagedRulesStatus?.rulesNotInstalled, - prePackagedRulesStatus?.rulesNotUpdated, - ] - ); - - const prePackagedTimelineStatus = useMemo( - () => - getPrePackagedTimelineStatus( - prePackagedRulesStatus?.timelinesInstalled, - prePackagedRulesStatus?.timelinesNotInstalled, - prePackagedRulesStatus?.timelinesNotUpdated - ), - [ - prePackagedRulesStatus?.timelinesInstalled, - prePackagedRulesStatus?.timelinesNotInstalled, - prePackagedRulesStatus?.timelinesNotUpdated, - ] - ); - const getLoadPrebuiltRulesAndTemplatesButton = useCallback( - ({ isDisabled, onClick, fill, 'data-test-subj': dataTestSubj = 'loadPrebuiltRulesBtn' }) => { - return (prePackagedAssetsStatus === 'ruleNotInstalled' || - prePackagedTimelineStatus === 'timelinesNotInstalled') && - prePackagedAssetsStatus !== 'someRuleUninstall' ? ( - <EuiButton - fill={fill} - iconType="indexOpen" - isLoading={loadingCreatePrePackagedRules} - isDisabled={isDisabled} - onClick={onClick} - data-test-subj={dataTestSubj} - > - {prePackagedAssetsStatus === 'ruleNotInstalled' && - prePackagedTimelineStatus === 'timelinesNotInstalled' && - i18n.LOAD_PREPACKAGED_RULES_AND_TEMPLATES} - - {prePackagedAssetsStatus === 'ruleNotInstalled' && - prePackagedTimelineStatus !== 'timelinesNotInstalled' && - i18n.LOAD_PREPACKAGED_RULES} - - {prePackagedAssetsStatus !== 'ruleNotInstalled' && - prePackagedTimelineStatus === 'timelinesNotInstalled' && - i18n.LOAD_PREPACKAGED_TIMELINE_TEMPLATES} - </EuiButton> - ) : null; - }, - [loadingCreatePrePackagedRules, prePackagedAssetsStatus, prePackagedTimelineStatus] - ); - - const getMissingRulesOrTimelinesButtonTitle = useCallback( - (missingRules: number, missingTimelines: number) => { - if (missingRules > 0 && missingTimelines === 0) - return i18n.RELOAD_MISSING_PREPACKAGED_RULES(missingRules); - else if (missingRules === 0 && missingTimelines > 0) - return i18n.RELOAD_MISSING_PREPACKAGED_TIMELINES(missingTimelines); - else if (missingRules > 0 && missingTimelines > 0) - return i18n.RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES(missingRules, missingTimelines); - }, - [] - ); - - const getReloadPrebuiltRulesAndTemplatesButton = useCallback( - ({ isDisabled, onClick, fill = false }) => { - return prePackagedAssetsStatus === 'someRuleUninstall' || - prePackagedTimelineStatus === 'someTimelineUninstall' ? ( - <EuiButton - fill={fill} - iconType="plusInCircle" - isLoading={loadingCreatePrePackagedRules} - isDisabled={isDisabled} - onClick={onClick} - data-test-subj="reloadPrebuiltRulesBtn" - > - {getMissingRulesOrTimelinesButtonTitle( - prePackagedRulesStatus?.rulesNotInstalled ?? 0, - prePackagedRulesStatus?.timelinesNotInstalled ?? 0 - )} - </EuiButton> - ) : null; - }, - [ - getMissingRulesOrTimelinesButtonTitle, - loadingCreatePrePackagedRules, - prePackagedAssetsStatus, - prePackagedRulesStatus?.rulesNotInstalled, - prePackagedRulesStatus?.timelinesNotInstalled, - prePackagedTimelineStatus, - ] - ); - - return { - loading: isFetching, - loadingCreatePrePackagedRules, - createPrePackagedRules, - getLoadPrebuiltRulesAndTemplatesButton, - getReloadPrebuiltRulesAndTemplatesButton, - ...prePackagedRulesStatus, - }; -}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules_status.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules_status.ts deleted file mode 100644 index c01bce4fe8bc2..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules_status.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 { useCallback } from 'react'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { getPrePackagedRulesStatus } from './api'; -import * as i18n from './translations'; - -const ONE_MINUTE = 60000; - -export interface PrePackagedRulesStatusResponse { - rulesCustomInstalled: number; - rulesInstalled: number; - rulesNotInstalled: number; - rulesNotUpdated: number; - timelinesInstalled: number; - timelinesNotInstalled: number; - timelinesNotUpdated: number; -} - -export const PRE_PACKAGED_RULES_STATUS_QUERY_KEY = 'prePackagedRulesStatus'; - -export const usePrePackagedRulesStatus = () => { - const { addError } = useAppToasts(); - - return useQuery<PrePackagedRulesStatusResponse>( - [PRE_PACKAGED_RULES_STATUS_QUERY_KEY], - async ({ signal }) => { - const response = await getPrePackagedRulesStatus({ signal }); - - return { - rulesCustomInstalled: response.rules_custom_installed, - rulesInstalled: response.rules_installed, - rulesNotInstalled: response.rules_not_installed, - rulesNotUpdated: response.rules_not_updated, - timelinesInstalled: response.timelines_installed, - timelinesNotInstalled: response.timelines_not_installed, - timelinesNotUpdated: response.timelines_not_updated, - }; - }, - { - staleTime: ONE_MINUTE * 5, - onError: (err) => { - addError(err, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); - }, - } - ); -}; - -/** - * We should use this hook to invalidate the prepackaged rules cache. For - * example, rule mutations that affect rule set size, like creation or deletion, - * should lead to cache invalidation. - * - * @returns A rules cache invalidation callback - */ -export const useInvalidatePrePackagedRulesStatus = () => { - const queryClient = useQueryClient(); - - return useCallback(() => { - queryClient.invalidateQueries([PRE_PACKAGED_RULES_STATUS_QUERY_KEY], { - refetchType: 'active', - }); - }, [queryClient]); -}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx deleted file mode 100644 index 89e32793b9e9a..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx +++ /dev/null @@ -1,104 +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 { renderHook, act } from '@testing-library/react-hooks'; -import type { ReturnRule } from './use_rule'; -import { useRule } from './use_rule'; -import * as api from './api'; -import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; - -jest.mock('./api'); -jest.mock('../../../../common/hooks/use_app_toasts'); - -describe('useRule', () => { - (useAppToasts as jest.Mock).mockReturnValue(useAppToastsMock.create()); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('init', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, ReturnRule>(() => - useRule('myOwnRuleID') - ); - await waitForNextUpdate(); - expect(result.current).toEqual([true, null]); - }); - }); - - test('fetch rule', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<string, ReturnRule>(() => - useRule('myOwnRuleID') - ); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current).toEqual([ - false, - { - actions: [], - author: [], - created_at: 'mm/dd/yyyyTHH:MM:sssz', - created_by: 'mockUser', - description: 'some desc', - enabled: true, - false_positives: [], - filters: [], - from: 'now-360s', - id: '12345678987654321', - immutable: false, - index: [ - 'apm-*-transaction*', - 'traces-apm*', - 'auditbeat-*', - 'endgame-*', - 'filebeat-*', - 'packetbeat-*', - 'winlogbeat-*', - ], - interval: '5m', - language: 'kuery', - name: 'Test rule', - max_signals: 100, - query: "user.email: 'root@elastic.co'", - references: [], - related_integrations: [], - required_fields: [], - risk_score: 75, - risk_score_mapping: [], - rule_id: 'bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf', - setup: '', - severity: 'high', - severity_mapping: [], - tags: ['APM'], - threat: [], - throttle: null, - to: 'now', - type: 'query', - updated_at: 'mm/dd/yyyyTHH:MM:sssz', - updated_by: 'mockUser', - }, - ]); - }); - }); - - test('fetch a new rule', async () => { - const spyOnfetchRuleById = jest.spyOn(api, 'fetchRuleById'); - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook<string, ReturnRule>((id) => useRule(id), { - initialProps: 'myOwnRuleID', - }); - await waitForNextUpdate(); - await waitForNextUpdate(); - rerender('newRuleId'); - await waitForNextUpdate(); - expect(spyOnfetchRuleById).toHaveBeenCalledTimes(2); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.tsx deleted file mode 100644 index bef089555128d..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.tsx +++ /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 { useEffect, useState } from 'react'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; - -import { fetchRuleById } from './api'; -import { transformInput } from './transforms'; -import * as i18n from './translations'; -import type { Rule } from './types'; - -export type ReturnRule = [boolean, Rule | null]; - -/** - * Hook for using to get a Rule from the Detection Engine API - * - * @param id desired Rule ID's (not rule_id) - * - */ -export const useRule = (id: string | undefined): ReturnRule => { - const [rule, setRule] = useState<Rule | null>(null); - const [loading, setLoading] = useState(true); - const { addError } = useAppToasts(); - - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - - const fetchData = async (idToFetch: string) => { - try { - setLoading(true); - const ruleResponse = transformInput( - await fetchRuleById({ - id: idToFetch, - signal: abortCtrl.signal, - }) - ); - if (isSubscribed) { - setRule(ruleResponse); - } - } catch (error) { - if (isSubscribed) { - setRule(null); - addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); - } - } - if (isSubscribed) { - setLoading(false); - } - }; - if (id != null) { - fetchData(id); - } - return () => { - isSubscribed = false; - abortCtrl.abort(); - }; - }, [id, addError]); - - return [loading, rule]; -}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_async.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_async.tsx deleted file mode 100644 index ab93a50f12829..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_async.tsx +++ /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 { useEffect, useCallback } from 'react'; - -import { flow } from 'fp-ts/lib/function'; -import { useAsync, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; -import { useHttp } from '../../../../common/lib/kibana'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { pureFetchRuleById } from './api'; -import type { Rule } from './types'; -import * as i18n from './translations'; -import { transformInput } from './transforms'; - -export interface UseRuleAsync { - error: unknown; - loading: boolean; - refresh: () => void; - rule: Rule | null; -} - -const _fetchRule = flow(withOptionalSignal(pureFetchRuleById), async (rule: Promise<Rule>) => - transformInput(await rule) -); - -/** This does not use "_useRuleAsyncInternal" as that would deactivate the useHooks linter rule, so instead it has the word "Internal" post-pended */ -const useRuleAsyncInternal = () => useAsync(_fetchRule); - -export const useRuleAsync = (ruleId: string): UseRuleAsync => { - const { start, loading, result, error } = useRuleAsyncInternal(); - const http = useHttp(); - const { addError } = useAppToasts(); - - const fetch = useCallback(() => { - start({ id: ruleId, http }); - }, [http, ruleId, start]); - - // initial fetch - useEffect(() => { - fetch(); - }, [fetch]); - - // toast on error - useEffect(() => { - if (error != null) { - addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); - } - }, [addError, error]); - - return { error, loading, refresh: fetch, rule: result ?? null }; -}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx deleted file mode 100644 index c8e2bc3e98279..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_with_fallback.test.tsx +++ /dev/null @@ -1,378 +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 { renderHook, act } from '@testing-library/react-hooks'; -import type { SecurityAppError } from '@kbn/securitysolution-t-grid'; -import { alertsMock8x, alertMockEmptyResults } from '../alerts/mock'; -import type { AlertSearchResponse } from '../alerts/types'; -import { useRuleWithFallback } from './use_rule_with_fallback'; -import * as api from './api'; -import * as alertsAPI from '../alerts/api'; -import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; - -jest.mock('./api'); -jest.mock('../alerts/api'); -jest.mock('../../../../common/hooks/use_app_toasts'); -jest.mock('../../../../common/lib/kibana'); - -const mockNotFoundErrorForRule = () => { - (api.fetchRuleById as jest.Mock).mockImplementation(async () => { - const err = new Error('Not found') as SecurityAppError; - err.body = { status_code: 404, message: 'Rule Not found' }; - throw err; - }); -}; - -describe('useRuleWithFallback', () => { - (useAppToasts as jest.Mock).mockReturnValue(useAppToastsMock.create()); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should return initial state on mount', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useRuleWithFallback('testRuleId')); - await waitForNextUpdate(); - expect(result.current).toEqual({ - error: undefined, - isExistingRule: true, - loading: false, - refresh: expect.any(Function), - rule: null, - }); - }); - }); - - it('should return the rule if it exists', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => useRuleWithFallback('testRuleId')); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current).toMatchInlineSnapshot(` - Object { - "error": undefined, - "isExistingRule": true, - "loading": false, - "refresh": [Function], - "rule": Object { - "actions": Array [], - "author": Array [], - "created_at": "mm/dd/yyyyTHH:MM:sssz", - "created_by": "mockUser", - "description": "some desc", - "enabled": true, - "false_positives": Array [], - "filters": Array [], - "from": "now-360s", - "id": "12345678987654321", - "immutable": false, - "index": Array [ - "apm-*-transaction*", - "traces-apm*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "packetbeat-*", - "winlogbeat-*", - ], - "interval": "5m", - "language": "kuery", - "max_signals": 100, - "name": "Test rule", - "query": "user.email: 'root@elastic.co'", - "references": Array [], - "related_integrations": Array [], - "required_fields": Array [], - "risk_score": 75, - "risk_score_mapping": Array [], - "rule_id": "bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf", - "setup": "", - "severity": "high", - "severity_mapping": Array [], - "tags": Array [ - "APM", - ], - "threat": Array [], - "throttle": null, - "to": "now", - "type": "query", - "updated_at": "mm/dd/yyyyTHH:MM:sssz", - "updated_by": "mockUser", - }, - } - `); - }); - }); - - it("should fallback to fetching rule data from a 7.x signal if the rule doesn't exist", async () => { - mockNotFoundErrorForRule(); - await act(async () => { - const { result, waitForNextUpdate } = renderHook((id) => useRuleWithFallback(id), { - initialProps: 'testRuleId', - }); - await waitForNextUpdate(); - await waitForNextUpdate(); - - expect(result.current).toMatchInlineSnapshot(` - Object { - "error": [Error: Not found], - "isExistingRule": false, - "loading": false, - "refresh": [Function], - "rule": Object { - "created_at": "2020-02-12T19:49:29.417Z", - "created_by": "elastic", - "description": "matches most events", - "enabled": true, - "false_positives": Array [], - "filters": Array [], - "from": "now-360s", - "id": "2df3a613-f5a8-4b55-bf6a-487fc820b842", - "immutable": false, - "index": Array [ - "apm-*-transaction*", - "traces-apm*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "packetbeat-*", - "winlogbeat-*", - ], - "interval": "5m", - "language": "kuery", - "max_signals": 100, - "meta": Object { - "from": "1m", - }, - "name": "matches host.name exists", - "output_index": ".siem-signals-default", - "query": "host.name : *", - "references": Array [ - "https://google.com", - ], - "risk_score": 79, - "rule_id": "82b2b065-a2ee-49fc-9d6d-781a75c3d280", - "severity": "high", - "tags": Array [ - "host.name exists", - "for testing", - ], - "threat": Array [ - Object { - "framework": "MITRE ATT&CK", - "tactic": Object { - "id": "TA0006", - "name": "Credential Access", - "reference": "https://attack.mitre.org/tactics/TA0006", - }, - "technique": Array [ - Object { - "id": "T1110", - "name": "Brute Force", - "reference": "https://attack.mitre.org/techniques/T1110", - }, - Object { - "id": "T1098", - "name": "Account Manipulation", - "reference": "https://attack.mitre.org/techniques/T1098", - }, - Object { - "id": "T1081", - "name": "Credentials in Files", - "reference": "https://attack.mitre.org/techniques/T1081", - }, - ], - }, - Object { - "framework": "MITRE ATT&CK", - "tactic": Object { - "id": "TA0009", - "name": "Collection", - "reference": "https://attack.mitre.org/tactics/TA0009", - }, - "technique": Array [ - Object { - "id": "T1530", - "name": "Data from Cloud Storage Object", - "reference": "https://attack.mitre.org/techniques/T1530", - }, - ], - }, - ], - "to": "now", - "type": "query", - "updated_at": "2020-02-14T23:15:06.186Z", - "updated_by": "elastic", - "version": 1, - }, - } - `); - }); - }); - - it("should fallback to fetching rule data from an 8.0 alert if the rule doesn't exist", async () => { - // Override default mock coming from ../alerts/__mocks__/api.ts - const spy = jest.spyOn(alertsAPI, 'fetchQueryAlerts').mockImplementation(async () => { - return alertsMock8x as AlertSearchResponse; - }); - - mockNotFoundErrorForRule(); - - await act(async () => { - const { result, waitForNextUpdate } = renderHook((id) => useRuleWithFallback(id), { - initialProps: 'testRuleId', - }); - await waitForNextUpdate(); - await waitForNextUpdate(); - - expect(result.current).toMatchInlineSnapshot(` - Object { - "error": [Error: Not found], - "isExistingRule": false, - "loading": false, - "refresh": [Function], - "rule": Object { - "actions": Array [], - "author": Array [ - "author", - ], - "category": "Custom Query Rule", - "consumer": "siem", - "created_at": "2022-01-11T23:22:47.678Z", - "created_by": "elastic", - "description": "8.1: To Be Deleted", - "enabled": true, - "exceptions_list": Array [], - "false_positives": Array [ - "fp", - ], - "filters": Array [], - "from": "now-360s", - "immutable": false, - "index": Array [ - "apm-*-transaction*", - "traces-apm*", - "auditbeat-*", - "endgame-*", - "filebeat-*", - "logs-*", - "packetbeat-*", - "winlogbeat-*", - ], - "interval": "5m", - "language": "kuery", - "license": "license", - "max_signals": 100, - "meta": Object { - "from": "1m", - "kibana_siem_app_url": "http://localhost:5601/kbn/app/security", - }, - "name": "944edf04-ea2d-44f9-b89a-574e9a9301da", - "note": "Investigation guuuide", - "producer": "siem", - "query": "host.name:*", - "references": Array [ - "http://www.example.com/1", - ], - "risk_score": 37, - "risk_score_mapping": Array [ - Object { - "field": "Responses.process.pid", - "operator": "equals", - "value": "", - }, - ], - "rule_id": "a2490dbb-33f6-4b03-88d8-b7d009ef58db", - "rule_name_override": "host.id", - "rule_type_id": "siem.queryRule", - "severity": "low", - "severity_mapping": Array [ - Object { - "field": "host.name", - "operator": "equals", - "severity": "low", - "value": "", - }, - ], - "tags": Array [ - "8.0-tag", - ], - "threat": Array [ - Object { - "framework": "MITRE ATT&CK", - "tactic": Object { - "id": "TA0007", - "name": "Discovery", - "reference": "https://attack.mitre.org/tactics/TA0007", - }, - "technique": Array [ - Object { - "id": "T1217", - "name": "Browser Bookmark Discovery", - "reference": "https://attack.mitre.org/techniques/T1217", - "subtechnique": Array [], - }, - Object { - "id": "T1580", - "name": "Cloud Infrastructure Discovery", - "reference": "https://attack.mitre.org/techniques/T1580", - "subtechnique": Array [], - }, - Object { - "id": "T1033", - "name": "System Owner/User Discovery", - "reference": "https://attack.mitre.org/techniques/T1033", - "subtechnique": Array [], - }, - ], - }, - Object { - "framework": "MITRE ATT&CK", - "tactic": Object { - "id": "TA0007", - "name": "Discovery", - "reference": "https://attack.mitre.org/tactics/TA0007", - }, - "technique": Array [], - }, - ], - "to": "now", - "type": "query", - "updated_at": "2022-01-11T23:22:47.678Z", - "updated_by": "elastic", - "uuid": "63136880-7335-11ec-9f1b-9db9315083e9", - "version": 1, - }, - } - `); - }); - // Reset back to default mock coming from ../alerts/__mocks__/api.ts - spy.mockRestore(); - }); - - it('should return rule as null if fallback fetching from 8.0 alert returns empty results', async () => { - // Override default mock coming from ../alerts/__mocks__/api.ts - const spy = jest.spyOn(alertsAPI, 'fetchQueryAlerts').mockImplementation(async () => { - return alertMockEmptyResults; - }); - - mockNotFoundErrorForRule(); - - await act(async () => { - const { result, waitForNextUpdate } = renderHook((id) => useRuleWithFallback(id), { - initialProps: 'testRuleId', - }); - await waitForNextUpdate(); - - expect(result.current.rule).toBeNull(); - }); - // Reset back to default mock coming from ../alerts/__mocks__/api.ts - spy.mockRestore(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.test.tsx deleted file mode 100644 index 52b37d6c3d4bf..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.test.tsx +++ /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 { renderHook, act } from '@testing-library/react-hooks'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; -import type { ReturnTags } from './use_tags'; -import { useTags } from './use_tags'; - -jest.mock('./api'); -jest.mock('../../../../common/hooks/use_app_toasts'); - -describe('useTags', () => { - (useAppToasts as jest.Mock).mockReturnValue(useAppToastsMock.create()); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('init', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<void, ReturnTags>(() => useTags()); - await waitForNextUpdate(); - expect(result.current).toEqual([true, [], result.current[2]]); - }); - }); - - test('fetch tags', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<void, ReturnTags>(() => useTags()); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current).toEqual([ - false, - ['elastic', 'love', 'quality', 'code'], - result.current[2], - ]); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.tsx deleted file mode 100644 index 5f16cb593a516..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.tsx +++ /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 { noop } from 'lodash/fp'; -import { useEffect, useState, useRef } from 'react'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { fetchTags } from './api'; -import * as i18n from './translations'; - -export type ReturnTags = [boolean, string[], () => void]; - -/** - * Hook for using the list of Tags from the Detection Engine API - * - */ -export const useTags = (): ReturnTags => { - const [tags, setTags] = useState<string[]>([]); - const [loading, setLoading] = useState(true); - const reFetchTags = useRef<() => void>(noop); - const { addError } = useAppToasts(); - - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - - const fetchData = async () => { - setLoading(true); - try { - const fetchTagsResult = await fetchTags({ - signal: abortCtrl.signal, - }); - - if (isSubscribed) { - setTags(fetchTagsResult); - } - } catch (error) { - if (isSubscribed) { - addError(error, { title: i18n.TAG_FETCH_FAILURE }); - } - } - if (isSubscribed) { - setLoading(false); - } - }; - - fetchData(); - reFetchTags.current = fetchData; - - return () => { - isSubscribed = false; - abortCtrl.abort(); - }; - }, [addError]); - - return [loading, tags, reFetchTags.current]; -}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.test.tsx deleted file mode 100644 index c25242e03126e..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.test.tsx +++ /dev/null @@ -1,64 +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 { renderHook, act } from '@testing-library/react-hooks'; - -import type { ReturnUpdateRule } from './use_update_rule'; -import { useUpdateRule } from './use_update_rule'; -import { getUpdateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; -import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { TestProviders } from '../../../../common/mock'; - -jest.mock('./api'); -jest.mock('../../../../common/hooks/use_app_toasts'); - -describe('useUpdateRule', () => { - (useAppToasts as jest.Mock).mockReturnValue(useAppToastsMock.create()); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('init', async () => { - const { result } = renderHook<unknown, ReturnUpdateRule>(() => useUpdateRule(), { - wrapper: TestProviders, - }); - - expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]); - }); - - test('saving rule with isLoading === true', async () => { - await act(async () => { - const { result, rerender, waitForNextUpdate } = renderHook<void, ReturnUpdateRule>( - () => useUpdateRule(), - { - wrapper: TestProviders, - } - ); - await waitForNextUpdate(); - result.current[1](getUpdateRulesSchemaMock()); - rerender(); - expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]); - }); - }); - - test('saved rule with isSaved === true', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook<void, ReturnUpdateRule>( - () => useUpdateRule(), - { - wrapper: TestProviders, - } - ); - await waitForNextUpdate(); - result.current[1](getUpdateRulesSchemaMock()); - await waitForNextUpdate(); - expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.tsx deleted file mode 100644 index e0144ff8e88eb..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.tsx +++ /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 { Dispatch } from 'react'; -import { useEffect, useState } from 'react'; - -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import type { UpdateRulesSchema } from '../../../../../common/detection_engine/schemas/request'; - -import { transformOutput } from './transforms'; - -import { updateRule } from './api'; -import * as i18n from './translations'; -import { useInvalidateRules } from './use_find_rules_query'; - -interface UpdateRuleReturn { - isLoading: boolean; - isSaved: boolean; -} - -export type ReturnUpdateRule = [UpdateRuleReturn, Dispatch<UpdateRulesSchema | null>]; - -export const useUpdateRule = (): ReturnUpdateRule => { - const [rule, setRule] = useState<UpdateRulesSchema | null>(null); - const [isSaved, setIsSaved] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const { addError } = useAppToasts(); - const invalidateRules = useInvalidateRules(); - - useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); - setIsSaved(false); - const saveRule = async () => { - if (rule != null) { - try { - setIsLoading(true); - await updateRule({ rule: transformOutput(rule), signal: abortCtrl.signal }); - invalidateRules(); - if (isSubscribed) { - setIsSaved(true); - } - } catch (error) { - if (isSubscribed) { - addError(error, { title: i18n.RULE_ADD_FAILURE }); - } - } - if (isSubscribed) { - setIsLoading(false); - } - } - }; - - saveRule(); - return () => { - isSubscribed = false; - abortCtrl.abort(); - }; - }, [rule, addError, invalidateRules]); - - return [{ isLoading, isSaved }, setRule]; -}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.ts deleted file mode 100644 index d80835209010f..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.ts +++ /dev/null @@ -1,260 +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 { NavigateToAppOptions } from '@kbn/core/public'; -import { APP_UI_ID } from '../../../../../../common/constants'; -import type { BulkActionEditPayload } from '../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { BulkAction } from '../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import type { HTTPError } from '../../../../../../common/detection_engine/types'; -import { SecurityPageName } from '../../../../../app/types'; -import { getEditRuleUrl } from '../../../../../common/components/link_to/redirect_to_detection_engine'; -import type { UseAppToasts } from '../../../../../common/hooks/use_app_toasts'; -import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../../../common/lib/telemetry'; -import { downloadBlob } from '../../../../../common/utils/download_blob'; -import type { - BulkActionSummary, - BulkActionResponse, -} from '../../../../containers/detection_engine/rules'; -import { performBulkAction } from '../../../../containers/detection_engine/rules'; -import * as i18n from '../translations'; -import { getExportedRulesCounts } from './helpers'; -import type { RulesTableActions } from './rules_table/rules_table_context'; - -export const goToRuleEditPage = ( - ruleId: string, - navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise<void> -) => { - navigateToApp(APP_UI_ID, { - deepLinkId: SecurityPageName.rules, - path: getEditRuleUrl(ruleId ?? ''), - }); -}; - -type OnActionSuccessCallback = ( - toasts: UseAppToasts, - action: BulkAction, - summary: BulkActionSummary -) => void; - -type OnActionErrorCallback = (toasts: UseAppToasts, action: BulkAction, error: HTTPError) => void; - -interface BaseRulesBulkActionArgs { - visibleRuleIds?: string[]; - toasts: UseAppToasts; - search: { query: string } | { ids: string[] }; - payload?: { edit?: BulkActionEditPayload[] }; - onError?: OnActionErrorCallback; - onFinish?: () => void; - onSuccess?: OnActionSuccessCallback; - setLoadingRules?: RulesTableActions['setLoadingRules']; -} - -interface RulesBulkActionArgs extends BaseRulesBulkActionArgs { - action: Exclude<BulkAction, BulkAction.export>; -} -interface ExportRulesBulkActionArgs extends BaseRulesBulkActionArgs { - action: BulkAction.export; -} - -// export bulk actions API returns blob, the rest of actions returns BulkActionResponse object -// hence method overloading to make type safe calls -export async function executeRulesBulkAction(args: ExportRulesBulkActionArgs): Promise<Blob | null>; -export async function executeRulesBulkAction( - args: RulesBulkActionArgs -): Promise<BulkActionResponse | null>; -export async function executeRulesBulkAction({ - visibleRuleIds = [], - action, - setLoadingRules, - toasts, - search, - payload, - onSuccess = defaultSuccessHandler, - onError = defaultErrorHandler, - onFinish, -}: RulesBulkActionArgs | ExportRulesBulkActionArgs) { - let response: Blob | BulkActionResponse | null = null; - try { - setLoadingRules?.({ ids: visibleRuleIds, action }); - - if (action === BulkAction.export) { - // on successToast for export handles separately outside of action execution method - response = await performBulkAction({ ...search, action }); - } else { - response = await performBulkAction({ ...search, action, edit: payload?.edit }); - sendTelemetry(action, response); - onSuccess(toasts, action, response.attributes.summary); - } - } catch (error) { - onError(toasts, action, error); - } finally { - setLoadingRules?.({ ids: [], action: null }); - onFinish?.(); - } - return response; -} - -/** - * downloads exported rules, received from export action - * @param params.response - Blob results with exported rules - * @param params.toasts - {@link UseAppToasts} toasts service - * @param params.onSuccess - {@link OnActionSuccessCallback} optional toast to display when action successful - * @param params.onError - {@link OnActionErrorCallback} optional toast to display when action failed - */ -export async function downloadExportedRules({ - response, - toasts, - onSuccess = defaultSuccessHandler, - onError = defaultErrorHandler, -}: { - response: Blob; - toasts: UseAppToasts; - onSuccess?: OnActionSuccessCallback; - onError?: OnActionErrorCallback; -}) { - try { - downloadBlob(response, `${i18n.EXPORT_FILENAME}.ndjson`); - onSuccess(toasts, BulkAction.export, await getExportedRulesCounts(response)); - } catch (error) { - onError(toasts, BulkAction.export, error); - } -} - -/** - * executes bulk export action and downloads exported rules - * @param params - {@link ExportRulesBulkActionArgs} - */ -export async function bulkExportRules(params: ExportRulesBulkActionArgs) { - const response = await executeRulesBulkAction(params); - - // if response null, likely network error happened and export rules haven't been received - if (response) { - await downloadExportedRules({ response, toasts: params.toasts, onSuccess: params.onSuccess }); - } -} - -function defaultErrorHandler(toasts: UseAppToasts, action: BulkAction, error: HTTPError) { - // if response doesn't have number of failed rules, it means the whole bulk action failed - // and general error toast will be shown. Otherwise - error toast for partial failure - const summary = (error?.body as BulkActionResponse)?.attributes?.summary; - error.stack = JSON.stringify(error.body, null, 2); - - let title: string; - let toastMessage: string | undefined; - - switch (action) { - case BulkAction.export: - title = i18n.RULES_BULK_EXPORT_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_EXPORT_FAILURE_DESCRIPTION(summary.failed); - } - break; - case BulkAction.duplicate: - title = i18n.RULES_BULK_DUPLICATE_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_DUPLICATE_FAILURE_DESCRIPTION(summary.failed); - } - break; - case BulkAction.delete: - title = i18n.RULES_BULK_DELETE_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_DELETE_FAILURE_DESCRIPTION(summary.failed); - } - break; - case BulkAction.enable: - title = i18n.RULES_BULK_ENABLE_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_ENABLE_FAILURE_DESCRIPTION(summary.failed); - } - break; - case BulkAction.disable: - title = i18n.RULES_BULK_DISABLE_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_DISABLE_FAILURE_DESCRIPTION(summary.failed); - } - break; - case BulkAction.edit: - title = i18n.RULES_BULK_EDIT_FAILURE; - if (summary) { - toastMessage = i18n.RULES_BULK_EDIT_FAILURE_DESCRIPTION(summary.failed); - } - break; - } - - toasts.addError(error, { title, toastMessage }); -} - -const getExportSuccessToastMessage = (succeeded: number, total: number) => { - const message = [i18n.RULES_BULK_EXPORT_SUCCESS_DESCRIPTION(succeeded, total)]; - - // if not all rules are successfully exported it means there included prebuilt rules - // display message to users that prebuilt rules were excluded - if (total > succeeded) { - message.push(i18n.RULES_BULK_EXPORT_PREBUILT_RULES_EXCLUDED_DESCRIPTION); - } - - return message.join(' '); -}; - -async function defaultSuccessHandler( - toasts: UseAppToasts, - action: BulkAction, - summary: BulkActionSummary -) { - let title: string; - let text: string | undefined; - - switch (action) { - case BulkAction.export: - title = i18n.RULES_BULK_EXPORT_SUCCESS; - text = getExportSuccessToastMessage(summary.succeeded, summary.total); - break; - case BulkAction.duplicate: - title = i18n.RULES_BULK_DUPLICATE_SUCCESS; - text = i18n.RULES_BULK_DUPLICATE_SUCCESS_DESCRIPTION(summary.succeeded); - break; - case BulkAction.delete: - title = i18n.RULES_BULK_DELETE_SUCCESS; - text = i18n.RULES_BULK_DELETE_SUCCESS_DESCRIPTION(summary.succeeded); - break; - case BulkAction.enable: - title = i18n.RULES_BULK_ENABLE_SUCCESS; - text = i18n.RULES_BULK_ENABLE_SUCCESS_DESCRIPTION(summary.succeeded); - break; - case BulkAction.disable: - title = i18n.RULES_BULK_DISABLE_SUCCESS; - text = i18n.RULES_BULK_DISABLE_SUCCESS_DESCRIPTION(summary.succeeded); - break; - case BulkAction.edit: - title = i18n.RULES_BULK_EDIT_SUCCESS; - text = i18n.RULES_BULK_EDIT_SUCCESS_DESCRIPTION(summary.succeeded); - break; - } - - toasts.addSuccess({ title, text }); -} - -function sendTelemetry(action: BulkAction, response: BulkActionResponse) { - if (action === BulkAction.disable || action === BulkAction.enable) { - if (response.attributes.results.updated.some((rule) => rule.immutable)) { - track( - METRIC_TYPE.COUNT, - action === BulkAction.enable - ? TELEMETRY_EVENT.SIEM_RULE_ENABLED - : TELEMETRY_EVENT.SIEM_RULE_DISABLED - ); - } - if (response.attributes.results.updated.some((rule) => !rule.immutable)) { - track( - METRIC_TYPE.COUNT, - action === BulkAction.disable - ? TELEMETRY_EVENT.CUSTOM_RULE_ENABLED - : TELEMETRY_EVENT.CUSTOM_RULE_DISABLED - ); - } - } -} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx deleted file mode 100644 index 826cdca510a62..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx +++ /dev/null @@ -1,102 +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 { waitFor } from '@testing-library/react'; -import { mount, shallow } from 'enzyme'; -import React from 'react'; -import { useKibana } from '../../../../../common/lib/kibana'; -import { TestProviders } from '../../../../../common/mock'; -import '../../../../../common/mock/formatted_relative'; -import '../../../../../common/mock/match_media'; -import { AllRules } from '.'; - -jest.mock('../../../../../common/components/link_to'); -jest.mock('../../../../../common/lib/kibana'); -jest.mock('../../../../containers/detection_engine/rules'); -jest.mock('./rules_table/rules_table_context'); - -const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>; - -describe('AllRules', () => { - beforeEach(() => { - useKibanaMock().services.application.capabilities = { - navLinks: {}, - management: {}, - catalogue: {}, - actions: { show: true }, - }; - }); - - afterEach(() => { - jest.clearAllTimers(); - jest.clearAllMocks(); - }); - - it('renders correctly', () => { - const wrapper = shallow( - <AllRules - createPrePackagedRules={jest.fn()} - hasPermissions - loadingCreatePrePackagedRules={false} - rulesCustomInstalled={0} - rulesInstalled={0} - rulesNotInstalled={0} - rulesNotUpdated={0} - /> - ); - - expect(wrapper.find('RulesTables')).toHaveLength(1); - }); - - describe('tabs', () => { - it('renders all rules tab by default', async () => { - const wrapper = mount( - <TestProviders> - <AllRules - createPrePackagedRules={jest.fn()} - hasPermissions - loadingCreatePrePackagedRules={false} - rulesCustomInstalled={1} - rulesInstalled={0} - rulesNotInstalled={0} - rulesNotUpdated={0} - /> - </TestProviders> - ); - - await waitFor(() => { - expect(wrapper.exists('[data-test-subj="monitoring-table"]')).toBeFalsy(); - expect(wrapper.exists('[data-test-subj="rules-table"]')).toBeTruthy(); - }); - }); - }); - - it('renders monitoring tab when monitoring tab clicked', async () => { - const wrapper = mount( - <TestProviders> - <AllRules - createPrePackagedRules={jest.fn()} - hasPermissions - loadingCreatePrePackagedRules={false} - rulesCustomInstalled={1} - rulesInstalled={0} - rulesNotInstalled={0} - rulesNotUpdated={0} - /> - </TestProviders> - ); - - await waitFor(() => { - const monitoringTab = wrapper.find('[data-test-subj="allRulesTableTab-monitoring"] button'); - monitoringTab.simulate('click'); - - wrapper.update(); - expect(wrapper.exists('[data-test-subj="monitoring-table"]')).toBeTruthy(); - expect(wrapper.exists('[data-test-subj="rules-table"]')).toBeFalsy(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx deleted file mode 100644 index bcdcc4dac263e..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.tsx +++ /dev/null @@ -1,62 +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 React, { useState } from 'react'; -import { RulesTables } from './rules_tables'; -import { AllRulesTabs, RulesTableToolbar } from './rules_table_toolbar'; - -interface AllRulesProps { - createPrePackagedRules: () => void; - hasPermissions: boolean; - loadingCreatePrePackagedRules: boolean; - rulesCustomInstalled?: number; - rulesInstalled?: number; - rulesNotInstalled?: number; - rulesNotUpdated?: number; -} - -/** - * Table Component for displaying all Rules for a given cluster. Provides the ability to filter - * by name, sort by enabled, and perform the following actions: - * * Enable/Disable - * * Duplicate - * * Delete - * * Import/Export - */ -export const AllRules = React.memo<AllRulesProps>( - ({ - createPrePackagedRules, - hasPermissions, - loadingCreatePrePackagedRules, - rulesCustomInstalled, - rulesInstalled, - rulesNotInstalled, - rulesNotUpdated, - }) => { - const [activeTab, setActiveTab] = useState(AllRulesTabs.rules); - - return ( - <> - <RulesTableToolbar activeTab={activeTab} onTabChange={setActiveTab} /> - <EuiSpacer /> - <RulesTables - createPrePackagedRules={createPrePackagedRules} - hasPermissions={hasPermissions} - loadingCreatePrePackagedRules={loadingCreatePrePackagedRules} - rulesCustomInstalled={rulesCustomInstalled} - rulesInstalled={rulesInstalled} - rulesNotInstalled={rulesNotInstalled} - rulesNotUpdated={rulesNotUpdated} - selectedTab={activeTab} - /> - </> - ); - } -); - -AllRules.displayName = 'AllRules'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_actions.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_actions.test.tsx deleted file mode 100644 index 30c8ba7008367..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_actions.test.tsx +++ /dev/null @@ -1,83 +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 uuid from 'uuid'; -import { useAppToastsMock } from '../../../../../common/hooks/use_app_toasts.mock'; -import '../../../../../common/mock/match_media'; -import { goToRuleEditPage, executeRulesBulkAction } from './actions'; -import { getRulesTableActions } from './rules_table_actions'; -import { mockRule } from './__mocks__/mock'; - -jest.mock('./actions'); - -const executeRulesBulkActionMock = executeRulesBulkAction as jest.Mock; -const goToRuleEditPageMock = goToRuleEditPage as jest.Mock; - -describe('getRulesTableActions', () => { - const rule = mockRule(uuid.v4()); - const toasts = useAppToastsMock.create(); - const invalidateRules = jest.fn(); - const invalidatePrePackagedRulesStatus = jest.fn(); - const setLoadingRules = jest.fn(); - const startTransaction = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('duplicate rule onClick should call rule edit after the rule is duplicated', async () => { - const ruleDuplicate = mockRule('newRule'); - const navigateToApp = jest.fn(); - executeRulesBulkActionMock.mockImplementation(() => - Promise.resolve({ attributes: { results: { created: [ruleDuplicate] } } }) - ); - - const duplicateRulesActionObject = getRulesTableActions({ - toasts, - navigateToApp, - invalidateRules, - invalidatePrePackagedRulesStatus, - actionsPrivileges: true, - setLoadingRules, - startTransaction, - })[1]; - const duplicateRulesActionHandler = duplicateRulesActionObject.onClick; - expect(duplicateRulesActionHandler).toBeDefined(); - - await duplicateRulesActionHandler!(rule); - expect(executeRulesBulkAction).toHaveBeenCalledWith( - expect.objectContaining({ action: 'duplicate' }) - ); - expect(goToRuleEditPageMock).toHaveBeenCalledWith(ruleDuplicate.id, navigateToApp); - }); - - test('delete rule onClick should call refetch after the rule is deleted', async () => { - const navigateToApp = jest.fn(); - - const deleteRulesActionObject = getRulesTableActions({ - toasts, - navigateToApp, - invalidateRules, - invalidatePrePackagedRulesStatus, - actionsPrivileges: true, - setLoadingRules, - startTransaction, - })[3]; - const deleteRuleActionHandler = deleteRulesActionObject.onClick; - expect(deleteRuleActionHandler).toBeDefined(); - - await deleteRuleActionHandler!(rule); - expect(executeRulesBulkAction).toHaveBeenCalledTimes(1); - expect(executeRulesBulkAction).toHaveBeenCalledWith( - expect.objectContaining({ action: 'delete' }) - ); - expect(invalidateRules).toHaveBeenCalledTimes(1); - expect(executeRulesBulkActionMock.mock.invocationCallOrder[0]).toBeLessThan( - invalidateRules.mock.invocationCallOrder[0] - ); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_actions.tsx deleted file mode 100644 index cfd83c3ab408f..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_actions.tsx +++ /dev/null @@ -1,123 +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 { DefaultItemAction } from '@elastic/eui'; -import { EuiToolTip } from '@elastic/eui'; -import React from 'react'; -import type { NavigateToAppOptions } from '@kbn/core/public'; -import { BulkAction } from '../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import type { UseAppToasts } from '../../../../../common/hooks/use_app_toasts'; -import { canEditRuleWithActions } from '../../../../../common/utils/privileges'; -import type { Rule } from '../../../../containers/detection_engine/rules'; -import * as i18n from '../translations'; -import { executeRulesBulkAction, goToRuleEditPage, bulkExportRules } from './actions'; -import type { RulesTableActions } from './rules_table/rules_table_context'; -import type { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; -import { SINGLE_RULE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; - -type NavigateToApp = (appId: string, options?: NavigateToAppOptions | undefined) => Promise<void>; - -export const getRulesTableActions = ({ - toasts, - navigateToApp, - invalidateRules, - invalidatePrePackagedRulesStatus, - actionsPrivileges, - setLoadingRules, - startTransaction, -}: { - toasts: UseAppToasts; - navigateToApp: NavigateToApp; - invalidateRules: () => void; - invalidatePrePackagedRulesStatus: () => void; - actionsPrivileges: boolean; - setLoadingRules: RulesTableActions['setLoadingRules']; - startTransaction: ReturnType<typeof useStartTransaction>['startTransaction']; -}): Array<DefaultItemAction<Rule>> => [ - { - type: 'icon', - 'data-test-subj': 'editRuleAction', - description: i18n.EDIT_RULE_SETTINGS, - name: !actionsPrivileges ? ( - <EuiToolTip position="left" content={i18n.EDIT_RULE_SETTINGS_TOOLTIP}> - <>{i18n.EDIT_RULE_SETTINGS}</> - </EuiToolTip> - ) : ( - i18n.EDIT_RULE_SETTINGS - ), - icon: 'controlsHorizontal', - onClick: (rule: Rule) => goToRuleEditPage(rule.id, navigateToApp), - enabled: (rule: Rule) => canEditRuleWithActions(rule, actionsPrivileges), - }, - { - type: 'icon', - 'data-test-subj': 'duplicateRuleAction', - description: i18n.DUPLICATE_RULE, - icon: 'copy', - name: !actionsPrivileges ? ( - <EuiToolTip position="left" content={i18n.EDIT_RULE_SETTINGS_TOOLTIP}> - <>{i18n.DUPLICATE_RULE}</> - </EuiToolTip> - ) : ( - i18n.DUPLICATE_RULE - ), - enabled: (rule: Rule) => canEditRuleWithActions(rule, actionsPrivileges), - onClick: async (rule: Rule) => { - startTransaction({ name: SINGLE_RULE_ACTIONS.DUPLICATE }); - const result = await executeRulesBulkAction({ - action: BulkAction.duplicate, - setLoadingRules, - visibleRuleIds: [rule.id], - toasts, - search: { ids: [rule.id] }, - }); - invalidateRules(); - invalidatePrePackagedRulesStatus(); - const createdRules = result?.attributes.results.created; - if (createdRules?.length) { - goToRuleEditPage(createdRules[0].id, navigateToApp); - } - }, - }, - { - type: 'icon', - 'data-test-subj': 'exportRuleAction', - description: i18n.EXPORT_RULE, - icon: 'exportAction', - name: i18n.EXPORT_RULE, - onClick: async (rule: Rule) => { - startTransaction({ name: SINGLE_RULE_ACTIONS.EXPORT }); - await bulkExportRules({ - action: BulkAction.export, - setLoadingRules, - visibleRuleIds: [rule.id], - toasts, - search: { ids: [rule.id] }, - }); - }, - enabled: (rule: Rule) => !rule.immutable, - }, - { - type: 'icon', - 'data-test-subj': 'deleteRuleAction', - description: i18n.DELETE_RULE, - icon: 'trash', - name: i18n.DELETE_RULE, - onClick: async (rule: Rule) => { - startTransaction({ name: SINGLE_RULE_ACTIONS.DELETE }); - await executeRulesBulkAction({ - action: BulkAction.delete, - setLoadingRules, - visibleRuleIds: [rule.id], - toasts, - search: { ids: [rule.id] }, - }); - invalidateRules(); - invalidatePrePackagedRulesStatus(); - }, - }, -]; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx deleted file mode 100644 index 2ad9de45346c4..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx +++ /dev/null @@ -1,41 +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 React from 'react'; -import { mount } from 'enzyme'; - -import { RulesTableFilters } from './rules_table_filters'; -import { TestProviders } from '../../../../../../common/mock'; - -jest.mock('../rules_table/rules_table_context'); - -describe('RulesTableFilters', () => { - it('renders no numbers next to rule type button filter if none exist', async () => { - const wrapper = mount(<RulesTableFilters allTags={[]} />, { wrappingComponent: TestProviders }); - - expect(wrapper.find('[data-test-subj="showElasticRulesFilterButton"]').at(0).text()).toEqual( - 'Elastic rules' - ); - expect(wrapper.find('[data-test-subj="showCustomRulesFilterButton"]').at(0).text()).toEqual( - 'Custom rules' - ); - }); - - it('renders number of custom and prepackaged rules', async () => { - const wrapper = mount( - <RulesTableFilters rulesCustomInstalled={10} rulesInstalled={9} allTags={[]} />, - { wrappingComponent: TestProviders } - ); - - expect(wrapper.find('[data-test-subj="showElasticRulesFilterButton"]').at(0).text()).toEqual( - 'Elastic rules (9)' - ); - expect(wrapper.find('[data-test-subj="showCustomRulesFilterButton"]').at(0).text()).toEqual( - 'Custom rules (10)' - ); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx deleted file mode 100644 index 9165bdea5c533..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx +++ /dev/null @@ -1,379 +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 { - EuiBasicTable, - EuiConfirmModal, - EuiEmptyPrompt, - EuiLoadingContent, - EuiProgress, -} from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useRef } from 'react'; -import { AllRulesTabs } from './rules_table_toolbar'; -import { RULES_TABLE_PAGE_SIZE_OPTIONS } from '../../../../../../common/constants'; -import { Loader } from '../../../../../common/components/loader'; -import { useBoolState } from '../../../../../common/hooks/use_bool_state'; -import { useValueChanged } from '../../../../../common/hooks/use_value_changed'; -import { RULES_TABLE_ACTIONS } from '../../../../../common/lib/apm/user_actions'; -import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction'; -import { PrePackagedRulesPrompt } from '../../../../components/rules/pre_packaged_rules/load_empty_prompt'; -import type { Rule, RulesSortingFields } from '../../../../containers/detection_engine/rules'; -import { useTags } from '../../../../containers/detection_engine/rules/use_tags'; -import { getPrePackagedRuleStatus } from '../helpers'; -import * as i18n from '../translations'; -import type { EuiBasicTableOnChange } from '../types'; -import { useMonitoringColumns, useRulesColumns } from './use_columns'; -import { showRulesTable } from './helpers'; -import { useRulesTableContext } from './rules_table/rules_table_context'; -import { useAsyncConfirmation } from './rules_table/use_async_confirmation'; -import { RulesTableFilters } from './rules_table_filters/rules_table_filters'; -import { RulesTableUtilityBar } from './rules_table_utility_bar'; -import { useBulkActionsDryRun } from './bulk_actions/use_bulk_actions_dry_run'; -import { useBulkActionsConfirmation } from './bulk_actions/use_bulk_actions_confirmation'; -import { useBulkEditFormFlyout } from './bulk_actions/use_bulk_edit_form_flyout'; -import { BulkActionDryRunConfirmation } from './bulk_actions/bulk_action_dry_run_confirmation'; -import { BulkEditFlyout } from './bulk_actions/bulk_edit_flyout'; -import { useBulkActions } from './bulk_actions/use_bulk_actions'; - -const INITIAL_SORT_FIELD = 'enabled'; - -interface RulesTableProps { - createPrePackagedRules: () => void; - hasPermissions: boolean; - loadingCreatePrePackagedRules: boolean; - rulesCustomInstalled?: number; - rulesInstalled?: number; - rulesNotInstalled?: number; - rulesNotUpdated?: number; - selectedTab: AllRulesTabs; -} - -const NO_ITEMS_MESSAGE = ( - <EuiEmptyPrompt title={<h3>{i18n.NO_RULES}</h3>} titleSize="xs" body={i18n.NO_RULES_BODY} /> -); - -/** - * Table Component for displaying all Rules for a given cluster. Provides the ability to filter - * by name, sort by enabled, and perform the following actions: - * * Enable/Disable - * * Duplicate - * * Delete - * * Import/Export - */ -export const RulesTables = React.memo<RulesTableProps>( - ({ - createPrePackagedRules, - hasPermissions, - loadingCreatePrePackagedRules, - rulesCustomInstalled, - rulesInstalled, - rulesNotInstalled, - rulesNotUpdated, - selectedTab, - }) => { - const { startTransaction } = useStartTransaction(); - const tableRef = useRef<EuiBasicTable>(null); - const rulesTableContext = useRulesTableContext(); - - const { - state: { - rules, - filterOptions, - isActionInProgress, - isAllSelected, - isFetched, - isLoading, - isRefetching, - isRefreshOn, - loadingRuleIds, - loadingRulesAction, - pagination, - selectedRuleIds, - sortingOptions, - }, - actions: { - reFetchRules, - setIsAllSelected, - setIsRefreshOn, - setPage, - setPerPage, - setSelectedRuleIds, - setSortingOptions, - }, - } = rulesTableContext; - - const prePackagedRuleStatus = getPrePackagedRuleStatus( - rulesInstalled, - rulesNotInstalled, - rulesNotUpdated - ); - - const [, allTags, reFetchTags] = useTags(); - - useEffect(() => { - reFetchTags(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [rulesCustomInstalled, rulesInstalled]); - - const [isDeleteConfirmationVisible, showDeleteConfirmation, hideDeleteConfirmation] = - useBoolState(); - - const [confirmDeletion, handleDeletionConfirm, handleDeletionCancel] = useAsyncConfirmation({ - onInit: showDeleteConfirmation, - onFinish: hideDeleteConfirmation, - }); - - const { - bulkActionsDryRunResult, - bulkAction, - isBulkActionConfirmationVisible, - showBulkActionConfirmation, - cancelBulkActionConfirmation, - approveBulkActionConfirmation, - } = useBulkActionsConfirmation(); - - const { - bulkEditActionType, - isBulkEditFlyoutVisible, - handleBulkEditFormConfirm, - handleBulkEditFormCancel, - completeBulkEditForm, - } = useBulkEditFormFlyout(); - - const selectedItemsCount = isAllSelected ? pagination.total : selectedRuleIds.length; - - const { isBulkActionsDryRunLoading, executeBulkActionsDryRun } = useBulkActionsDryRun(); - - const getBulkItemsPopoverContent = useBulkActions({ - filterOptions, - confirmDeletion, - showBulkActionConfirmation, - completeBulkEditForm, - reFetchTags, - executeBulkActionsDryRun, - }); - - const paginationMemo = useMemo( - () => ({ - pageIndex: pagination.page - 1, - pageSize: pagination.perPage, - totalItemCount: pagination.total, - pageSizeOptions: RULES_TABLE_PAGE_SIZE_OPTIONS, - }), - [pagination] - ); - - const tableOnChangeCallback = useCallback( - ({ page, sort }: EuiBasicTableOnChange) => { - setSortingOptions({ - field: (sort?.field as RulesSortingFields) ?? INITIAL_SORT_FIELD, // Narrowing EuiBasicTable sorting types - order: sort?.direction ?? 'desc', - }); - setPage(page.index + 1); - setPerPage(page.size); - }, - [setPage, setPerPage, setSortingOptions] - ); - - const rulesColumns = useRulesColumns({ hasPermissions }); - const monitoringColumns = useMonitoringColumns({ hasPermissions }); - - const handleCreatePrePackagedRules = useCallback(async () => { - if (createPrePackagedRules != null) { - startTransaction({ name: RULES_TABLE_ACTIONS.LOAD_PREBUILT }); - await createPrePackagedRules(); - await reFetchRules(); - } - }, [createPrePackagedRules, reFetchRules, startTransaction]); - - const handleRefreshRules = useCallback(() => { - startTransaction({ name: RULES_TABLE_ACTIONS.REFRESH }); - reFetchRules(); - }, [reFetchRules, startTransaction]); - - const isSelectAllCalled = useRef(false); - - // Synchronize selectedRuleIds with EuiBasicTable's selected rows - useValueChanged((ruleIds) => { - if (tableRef.current != null) { - tableRef.current.setSelection(rules.filter((rule) => ruleIds.includes(rule.id))); - } - }, selectedRuleIds); - - const euiBasicTableSelectionProps = useMemo( - () => ({ - selectable: (item: Rule) => !loadingRuleIds.includes(item.id), - onSelectionChange: (selected: Rule[]) => { - /** - * EuiBasicTable doesn't provide declarative API to control selected rows. - * This limitation requires us to synchronize selection state manually using setSelection(). - * But it creates a chain reaction when the user clicks Select All: - * selectAll() -> setSelection() -> onSelectionChange() -> setSelection(). - * To break the chain we should check whether the onSelectionChange was triggered - * by the Select All action or not. - * - */ - if (isSelectAllCalled.current) { - isSelectAllCalled.current = false; - // Handle special case of unselecting all rules via checkbox - // after all rules were selected via Bulk select. - if (selected.length === 0) { - setIsAllSelected(false); - setSelectedRuleIds([]); - } - } else { - setSelectedRuleIds(selected.map(({ id }) => id)); - setIsAllSelected(false); - } - }, - }), - [loadingRuleIds, setIsAllSelected, setSelectedRuleIds] - ); - - const toggleSelectAll = useCallback(() => { - isSelectAllCalled.current = true; - setIsAllSelected(!isAllSelected); - setSelectedRuleIds(!isAllSelected ? rules.map(({ id }) => id) : []); - }, [rules, isAllSelected, setIsAllSelected, setSelectedRuleIds]); - - const handleAutoRefreshSwitch = useCallback( - (refreshOn: boolean) => { - if (refreshOn) { - reFetchRules(); - } - setIsRefreshOn(refreshOn); - }, - [setIsRefreshOn, reFetchRules] - ); - - const shouldShowRulesTable = useMemo( - (): boolean => showRulesTable({ rulesCustomInstalled, rulesInstalled }) && !isLoading, - [isLoading, rulesCustomInstalled, rulesInstalled] - ); - - const shouldShowPrepackagedRulesPrompt = useMemo( - (): boolean => - rulesCustomInstalled != null && - rulesCustomInstalled === 0 && - prePackagedRuleStatus === 'ruleNotInstalled' && - !isLoading, - [isLoading, prePackagedRuleStatus, rulesCustomInstalled] - ); - - const tableProps = - selectedTab === AllRulesTabs.rules - ? { - 'data-test-subj': 'rules-table', - columns: rulesColumns, - } - : { 'data-test-subj': 'monitoring-table', columns: monitoringColumns }; - - const shouldShowLinearProgress = isFetched && isRefetching; - const shouldShowLoadingOverlay = (!isFetched && isRefetching) || isActionInProgress; - - return ( - <> - {shouldShowLinearProgress && ( - <EuiProgress - data-test-subj="loadingRulesInfoProgress" - size="xs" - position="absolute" - color="accent" - /> - )} - {shouldShowLoadingOverlay && ( - <Loader data-test-subj="loadingPanelAllRulesTable" overlay size="xl" /> - )} - {shouldShowRulesTable && ( - <RulesTableFilters - rulesCustomInstalled={rulesCustomInstalled} - rulesInstalled={rulesInstalled} - allTags={allTags} - /> - )} - {shouldShowPrepackagedRulesPrompt && ( - <PrePackagedRulesPrompt - createPrePackagedRules={handleCreatePrePackagedRules} - loading={loadingCreatePrePackagedRules} - userHasPermissions={hasPermissions} - /> - )} - {isLoading && ( - <EuiLoadingContent data-test-subj="initialLoadingPanelAllRulesTable" lines={10} /> - )} - {isDeleteConfirmationVisible && ( - <EuiConfirmModal - title={i18n.DELETE_CONFIRMATION_TITLE} - onCancel={handleDeletionCancel} - onConfirm={handleDeletionConfirm} - confirmButtonText={i18n.DELETE_CONFIRMATION_CONFIRM} - cancelButtonText={i18n.DELETE_CONFIRMATION_CANCEL} - buttonColor="danger" - defaultFocusedButton="confirm" - data-test-subj="allRulesDeleteConfirmationModal" - > - <p>{i18n.DELETE_CONFIRMATION_BODY}</p> - </EuiConfirmModal> - )} - {isBulkActionConfirmationVisible && bulkAction && ( - <BulkActionDryRunConfirmation - bulkAction={bulkAction} - result={bulkActionsDryRunResult} - onCancel={cancelBulkActionConfirmation} - onConfirm={approveBulkActionConfirmation} - /> - )} - {isBulkEditFlyoutVisible && bulkEditActionType !== undefined && ( - <BulkEditFlyout - rulesCount={bulkActionsDryRunResult?.succeededRulesCount ?? 0} - editAction={bulkEditActionType} - onClose={handleBulkEditFormCancel} - onConfirm={handleBulkEditFormConfirm} - tags={allTags} - /> - )} - {shouldShowRulesTable && ( - <> - <RulesTableUtilityBar - canBulkEdit={hasPermissions} - pagination={pagination} - numberSelectedItems={selectedItemsCount} - onGetBulkItemsPopoverContent={getBulkItemsPopoverContent} - onRefresh={handleRefreshRules} - isAutoRefreshOn={isRefreshOn} - onRefreshSwitch={handleAutoRefreshSwitch} - isAllSelected={isAllSelected} - onToggleSelectAll={toggleSelectAll} - isBulkActionInProgress={isBulkActionsDryRunLoading || loadingRulesAction != null} - hasDisabledActions={loadingRulesAction != null} - /> - <EuiBasicTable - itemId="id" - items={rules} - isSelectable={hasPermissions} - noItemsMessage={NO_ITEMS_MESSAGE} - onChange={tableOnChangeCallback} - pagination={paginationMemo} - ref={tableRef} - selection={hasPermissions ? euiBasicTableSelectionProps : undefined} - sorting={{ - sort: { - // EuiBasicTable has incorrect `sort.field` types which accept only `keyof Item` and reject fields in dot notation - field: sortingOptions.field as keyof Rule, - direction: sortingOptions.order, - }, - }} - {...tableProps} - /> - </> - )} - </> - ); - } -); - -RulesTables.displayName = 'RulesTables'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/components/edit_rule_settings_button_link.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/components/edit_rule_settings_button_link.tsx new file mode 100644 index 0000000000000..6ef7ebb0cae4c --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/components/edit_rule_settings_button_link.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import { EuiToolTip } from '@elastic/eui'; +import { useKibana } from '../../../../../../common/lib/kibana'; +import { SecuritySolutionLinkButton } from '../../../../../../common/components/links'; +import { APP_UI_ID } from '../../../../../../../common/constants'; +import { SecurityPageName } from '../../../../../../app/types'; +import { getEditRuleUrl } from '../../../../../../common/components/link_to/redirect_to_detection_engine'; +import * as ruleI18n from '../../translations'; + +interface EditRuleSettingButtonLinkProps { + ruleId: string; + disabled: boolean; + disabledReason?: string; +} + +export function EditRuleSettingButtonLink({ + ruleId, + disabled = false, + disabledReason, +}: EditRuleSettingButtonLinkProps): JSX.Element { + const { + application: { navigateToApp }, + } = useKibana().services; + const goToEditRule = useCallback( + (ev) => { + ev.preventDefault(); + navigateToApp(APP_UI_ID, { + deepLinkId: SecurityPageName.rules, + path: getEditRuleUrl(ruleId), + }); + }, + [navigateToApp, ruleId] + ); + + return ( + <EuiToolTip position="top" content={disabledReason}> + <SecuritySolutionLinkButton + data-test-subj="editRuleSettingsLink" + onClick={goToEditRule} + iconType="controlsHorizontal" + isDisabled={disabled} + deepLinkId={SecurityPageName.rules} + path={getEditRuleUrl(ruleId)} + > + {ruleI18n.EDIT_RULE_SETTINGS} + </SecuritySolutionLinkButton> + </EuiToolTip> + ); +} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx deleted file mode 100644 index d283879045165..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx +++ /dev/null @@ -1,54 +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 React from 'react'; -import { shallow } from 'enzyme'; - -import '../../../../../common/mock/match_media'; -import { TestProviders } from '../../../../../common/mock'; -import { EditRulePage } from '.'; -import { useUserData } from '../../../../components/user_info'; -import { useParams } from 'react-router-dom'; -import { useAppToastsMock } from '../../../../../common/hooks/use_app_toasts.mock'; -import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; - -jest.mock('../../../../../common/lib/kibana'); -jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); -jest.mock('../../../../containers/detection_engine/rules/use_find_rules_query'); -jest.mock('../../../../../common/components/link_to'); -jest.mock('../../../../components/user_info'); -jest.mock('react-router-dom', () => { - const originalModule = jest.requireActual('react-router-dom'); - - return { - ...originalModule, - useHistory: jest.fn(), - useParams: jest.fn(), - }; -}); -jest.mock('../../../../../common/hooks/use_app_toasts'); -jest.mock('../use_get_saved_query', () => ({ - __esModule: true, - useGetSavedQuery: jest.fn().mockReturnValue({}), -})); - -describe('EditRulePage', () => { - let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>; - - beforeEach(() => { - appToastsMock = useAppToastsMock.create(); - (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); - }); - - it('renders correctly', () => { - (useUserData as jest.Mock).mockReturnValue([{}]); - (useParams as jest.Mock).mockReturnValue({}); - const wrapper = shallow(<EditRulePage />, { wrappingComponent: TestProviders }); - - expect(wrapper.find('[title="Edit rule settings"]')).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx index 4392be5cdc409..82934d0cccac2 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx @@ -15,16 +15,18 @@ import { getActionsStepsData, getHumanizedDuration, getModifiedAboutDetailsData, - getPrePackagedRuleStatus, - getPrePackagedTimelineStatus, + getPrePackagedRuleInstallationStatus, + getPrePackagedTimelineInstallationStatus, determineDetailsValue, - userHasPermissions, fillEmptySeverityMappings, } from './helpers'; -import { mockRuleWithEverything, mockRule } from './all/__mocks__/mock'; +import { + mockRuleWithEverything, + mockRule, +} from '../../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock'; import { FilterStateStore } from '@kbn/es-query'; -import type { Rule } from '../../../containers/detection_engine/rules'; +import type { Rule } from '../../../../detection_engine/rule_management/logic'; import type { AboutStepRule, AboutStepRuleDetails, @@ -448,35 +450,12 @@ describe('rule helpers', () => { }); }); - describe('userHasPermissions', () => { - test("returns true when user's CRUD operations are null", () => { - const result: boolean = userHasPermissions(null); - const userHasPermissionsExpectedResult = true; - - expect(result).toEqual(userHasPermissionsExpectedResult); - }); - - test('returns false when user cannot CRUD', () => { - const result: boolean = userHasPermissions(false); - const userHasPermissionsExpectedResult = false; - - expect(result).toEqual(userHasPermissionsExpectedResult); - }); - - test('returns true when user can CRUD', () => { - const result: boolean = userHasPermissions(true); - const userHasPermissionsExpectedResult = true; - - expect(result).toEqual(userHasPermissionsExpectedResult); - }); - }); - describe('getPrePackagedRuleStatus', () => { test('ruleNotInstalled', () => { const rulesInstalled = 0; const rulesNotInstalled = 1; const rulesNotUpdated = 0; - const result: string = getPrePackagedRuleStatus( + const result: string = getPrePackagedRuleInstallationStatus( rulesInstalled, rulesNotInstalled, rulesNotUpdated @@ -489,7 +468,7 @@ describe('rule helpers', () => { const rulesInstalled = 1; const rulesNotInstalled = 0; const rulesNotUpdated = 0; - const result: string = getPrePackagedRuleStatus( + const result: string = getPrePackagedRuleInstallationStatus( rulesInstalled, rulesNotInstalled, rulesNotUpdated @@ -502,7 +481,7 @@ describe('rule helpers', () => { const rulesInstalled = 1; const rulesNotInstalled = 1; const rulesNotUpdated = 0; - const result: string = getPrePackagedRuleStatus( + const result: string = getPrePackagedRuleInstallationStatus( rulesInstalled, rulesNotInstalled, rulesNotUpdated @@ -515,7 +494,7 @@ describe('rule helpers', () => { const rulesInstalled = 1; const rulesNotInstalled = 0; const rulesNotUpdated = 1; - const result: string = getPrePackagedRuleStatus( + const result: string = getPrePackagedRuleInstallationStatus( rulesInstalled, rulesNotInstalled, rulesNotUpdated @@ -528,7 +507,7 @@ describe('rule helpers', () => { const rulesInstalled = undefined; const rulesNotInstalled = undefined; const rulesNotUpdated = undefined; - const result: string = getPrePackagedRuleStatus( + const result: string = getPrePackagedRuleInstallationStatus( rulesInstalled, rulesNotInstalled, rulesNotUpdated @@ -543,7 +522,7 @@ describe('rule helpers', () => { const timelinesInstalled = 0; const timelinesNotInstalled = 1; const timelinesNotUpdated = 0; - const result: string = getPrePackagedTimelineStatus( + const result: string = getPrePackagedTimelineInstallationStatus( timelinesInstalled, timelinesNotInstalled, timelinesNotUpdated @@ -556,7 +535,7 @@ describe('rule helpers', () => { const timelinesInstalled = 1; const timelinesNotInstalled = 0; const timelinesNotUpdated = 0; - const result: string = getPrePackagedTimelineStatus( + const result: string = getPrePackagedTimelineInstallationStatus( timelinesInstalled, timelinesNotInstalled, timelinesNotUpdated @@ -569,7 +548,7 @@ describe('rule helpers', () => { const timelinesInstalled = 1; const timelinesNotInstalled = 1; const timelinesNotUpdated = 0; - const result: string = getPrePackagedTimelineStatus( + const result: string = getPrePackagedTimelineInstallationStatus( timelinesInstalled, timelinesNotInstalled, timelinesNotUpdated @@ -582,7 +561,7 @@ describe('rule helpers', () => { const timelinesInstalled = 1; const timelinesNotInstalled = 0; const timelinesNotUpdated = 1; - const result: string = getPrePackagedTimelineStatus( + const result: string = getPrePackagedTimelineInstallationStatus( timelinesInstalled, timelinesNotInstalled, timelinesNotUpdated @@ -595,7 +574,7 @@ describe('rule helpers', () => { const timelinesInstalled = undefined; const timelinesNotInstalled = undefined; const timelinesNotUpdated = undefined; - const result: string = getPrePackagedTimelineStatus( + const result: string = getPrePackagedTimelineInstallationStatus( timelinesInstalled, timelinesNotInstalled, timelinesNotUpdated diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index 36e00aaccc1ef..216e202052ee7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -13,10 +13,10 @@ import { useLocation } from 'react-router-dom'; import styled from 'styled-components'; import { EuiFlexItem } from '@elastic/eui'; import type { + Severity, + SeverityMapping, Threats, Type, - SeverityMapping, - Severity, } from '@kbn/securitysolution-io-ts-alerting-types'; import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; import type { Filter } from '@kbn/es-query'; @@ -29,7 +29,7 @@ import { transformRuleToAlertAction, transformRuleToAlertResponseAction, } from '../../../../../common/detection_engine/transform_actions'; -import type { Rule } from '../../../containers/detection_engine/rules'; +import type { Rule } from '../../../../detection_engine/rule_management/logic'; import type { AboutStepRule, AboutStepRuleDetails, @@ -266,25 +266,25 @@ export const getModifiedAboutDetailsData = (rule: Rule): AboutStepRuleDetails => export const useQuery = () => new URLSearchParams(useLocation().search); -export type PrePackagedRuleStatus = +export type PrePackagedRuleInstallationStatus = | 'ruleInstalled' | 'ruleNotInstalled' | 'ruleNeedUpdate' | 'someRuleUninstall' | 'unknown'; -export type PrePackagedTimelineStatus = +export type PrePackagedTimelineInstallationStatus = | 'timelinesNotInstalled' | 'timelinesInstalled' | 'someTimelineUninstall' | 'timelineNeedUpdate' | 'unknown'; -export const getPrePackagedRuleStatus = ( +export const getPrePackagedRuleInstallationStatus = ( rulesInstalled?: number, rulesNotInstalled?: number, rulesNotUpdated?: number -): PrePackagedRuleStatus => { +): PrePackagedRuleInstallationStatus => { if ( rulesNotInstalled != null && rulesInstalled === 0 && @@ -319,11 +319,11 @@ export const getPrePackagedRuleStatus = ( } return 'unknown'; }; -export const getPrePackagedTimelineStatus = ( +export const getPrePackagedTimelineInstallationStatus = ( timelinesInstalled?: number, timelinesNotInstalled?: number, timelinesNotUpdated?: number -): PrePackagedTimelineStatus => { +): PrePackagedTimelineInstallationStatus => { if ( timelinesNotInstalled != null && timelinesInstalled === 0 && @@ -461,10 +461,6 @@ export const getActionMessageParams = memoizeOne((ruleType: Type | undefined): A export const getAllActionMessageParams = () => transformRuleKeysToActionVariables(getAllRuleParamsKeys()); -// typed as null not undefined as the initial state for this value is null. -export const userHasPermissions = (canUserCRUD: boolean | null): boolean => - canUserCRUD != null ? canUserCRUD : true; - export const MaxWidthEuiFlexItem = styled(EuiFlexItem)` max-width: 1000px; overflow: hidden; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx deleted file mode 100644 index 997f40e918cb2..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx +++ /dev/null @@ -1,274 +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, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; -import { MlJobUpgradeModal } from '../../../components/modals/ml_job_upgrade_modal'; -import { affectedJobIds } from '../../../components/callouts/ml_job_compatibility_callout/affected_job_ids'; -import { useInstalledSecurityJobs } from '../../../../common/components/ml/hooks/use_installed_security_jobs'; - -import { usePrePackagedRules, importRules } from '../../../containers/detection_engine/rules'; -import { useListsConfig } from '../../../containers/detection_engine/lists/use_lists_config'; -import { getDetectionEngineUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; -import { SecuritySolutionPageWrapper } from '../../../../common/components/page_wrapper'; -import { SpyRoute } from '../../../../common/utils/route/spy_routes'; - -import { useUserData } from '../../../components/user_info'; -import { AllRules } from './all'; -import { ImportDataModal } from '../../../../common/components/import_data_modal'; -import { ValueListsFlyout } from '../../../components/value_lists_management_flyout'; -import { UpdatePrePackagedRulesCallOut } from '../../../components/rules/pre_packaged_rules/update_callout'; -import { - getPrePackagedRuleStatus, - getPrePackagedTimelineStatus, - redirectToDetections, - userHasPermissions, -} from './helpers'; -import * as i18n from './translations'; -import { SecurityPageName } from '../../../../app/types'; -import { SecuritySolutionLinkButton } from '../../../../common/components/links'; -import { NeedAdminForUpdateRulesCallOut } from '../../../components/callouts/need_admin_for_update_callout'; -import { MlJobCompatibilityCallout } from '../../../components/callouts/ml_job_compatibility_callout'; -import { MissingPrivilegesCallOut } from '../../../components/callouts/missing_privileges_callout'; -import { APP_UI_ID } from '../../../../../common/constants'; -import { useKibana } from '../../../../common/lib/kibana'; -import { HeaderPage } from '../../../../common/components/header_page'; -import { RulesTableContextProvider } from './all/rules_table/rules_table_context'; -import { useInvalidateRules } from '../../../containers/detection_engine/rules/use_find_rules_query'; -import { useBoolState } from '../../../../common/hooks/use_bool_state'; -import { RULES_TABLE_ACTIONS } from '../../../../common/lib/apm/user_actions'; -import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction'; - -const RulesPageComponent: React.FC = () => { - const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState(); - const [isValueListFlyoutVisible, showValueListFlyout, hideValueListFlyout] = useBoolState(); - const { navigateToApp } = useKibana().services.application; - const { startTransaction } = useStartTransaction(); - const invalidateRules = useInvalidateRules(); - - const { loading: loadingJobs, jobs } = useInstalledSecurityJobs(); - const legacyJobsInstalled = jobs.filter((job) => affectedJobIds.includes(job.id)); - const [isUpgradeModalVisible, setIsUpgradeModalVisible] = useState(false); - - const [ - { - loading: userInfoLoading, - isSignalIndexExists, - isAuthenticated, - hasEncryptionKey, - canUserCRUD, - hasIndexWrite, - }, - ] = useUserData(); - const { - loading: listsConfigLoading, - canWriteIndex: canWriteListsIndex, - needsConfiguration: needsListsConfiguration, - } = useListsConfig(); - const loading = userInfoLoading || listsConfigLoading; - const { - createPrePackagedRules, - loadingCreatePrePackagedRules, - rulesCustomInstalled, - rulesInstalled, - rulesNotInstalled, - rulesNotUpdated, - timelinesInstalled, - timelinesNotInstalled, - timelinesNotUpdated, - getLoadPrebuiltRulesAndTemplatesButton, - getReloadPrebuiltRulesAndTemplatesButton, - } = usePrePackagedRules({ - canUserCRUD, - hasIndexWrite, - isSignalIndexExists, - isAuthenticated, - hasEncryptionKey, - }); - const prePackagedRuleStatus = getPrePackagedRuleStatus( - rulesInstalled, - rulesNotInstalled, - rulesNotUpdated - ); - - const prePackagedTimelineStatus = getPrePackagedTimelineStatus( - timelinesInstalled, - timelinesNotInstalled, - timelinesNotUpdated - ); - - const handleCreatePrePackagedRules = useCallback(async () => { - if (createPrePackagedRules != null) { - startTransaction({ name: RULES_TABLE_ACTIONS.LOAD_PREBUILT }); - await createPrePackagedRules(); - } - }, [createPrePackagedRules, startTransaction]); - - // Wrapper to add confirmation modal for users who may be running older ML Jobs that would - // be overridden by updating their rules. For details, see: https://github.com/elastic/kibana/issues/128121 - const mlJobUpgradeModalConfirm = useCallback(async () => { - setIsUpgradeModalVisible(false); - await handleCreatePrePackagedRules(); - }, [handleCreatePrePackagedRules, setIsUpgradeModalVisible]); - - const showMlJobUpgradeModal = useCallback(async () => { - if (legacyJobsInstalled.length > 0) { - setIsUpgradeModalVisible(true); - } else { - await handleCreatePrePackagedRules(); - } - }, [handleCreatePrePackagedRules, legacyJobsInstalled.length]); - - const loadPrebuiltRulesAndTemplatesButton = useMemo( - () => - getLoadPrebuiltRulesAndTemplatesButton({ - isDisabled: !userHasPermissions(canUserCRUD) || loading || loadingJobs, - onClick: showMlJobUpgradeModal, - }), - [ - canUserCRUD, - getLoadPrebuiltRulesAndTemplatesButton, - showMlJobUpgradeModal, - loading, - loadingJobs, - ] - ); - - const reloadPrebuiltRulesAndTemplatesButton = useMemo( - () => - getReloadPrebuiltRulesAndTemplatesButton({ - isDisabled: !userHasPermissions(canUserCRUD) || loading || loadingJobs, - onClick: showMlJobUpgradeModal, - }), - [ - canUserCRUD, - getReloadPrebuiltRulesAndTemplatesButton, - showMlJobUpgradeModal, - loading, - loadingJobs, - ] - ); - - if ( - redirectToDetections( - isSignalIndexExists, - isAuthenticated, - hasEncryptionKey, - needsListsConfiguration - ) - ) { - navigateToApp(APP_UI_ID, { - deepLinkId: SecurityPageName.alerts, - path: getDetectionEngineUrl(), - }); - return null; - } - - return ( - <> - <NeedAdminForUpdateRulesCallOut /> - <MissingPrivilegesCallOut /> - <MlJobCompatibilityCallout /> - {isUpgradeModalVisible && ( - <MlJobUpgradeModal - jobs={legacyJobsInstalled} - onCancel={() => setIsUpgradeModalVisible(false)} - onConfirm={mlJobUpgradeModalConfirm} - /> - )} - <ValueListsFlyout showFlyout={isValueListFlyoutVisible} onClose={hideValueListFlyout} /> - <ImportDataModal - checkBoxLabel={i18n.OVERWRITE_WITH_SAME_NAME} - closeModal={hideImportModal} - description={i18n.SELECT_RULE} - errorMessage={i18n.IMPORT_FAILED} - failedDetailed={i18n.IMPORT_FAILED_DETAILED} - importComplete={invalidateRules} - importData={importRules} - successMessage={i18n.SUCCESSFULLY_IMPORTED_RULES} - showModal={isImportModalVisible} - submitBtnText={i18n.IMPORT_RULE_BTN_TITLE} - subtitle={i18n.INITIAL_PROMPT_TEXT} - title={i18n.IMPORT_RULE} - showExceptionsCheckBox - showCheckBox - /> - - <RulesTableContextProvider> - <SecuritySolutionPageWrapper> - <HeaderPage title={i18n.PAGE_TITLE}> - <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}> - {loadPrebuiltRulesAndTemplatesButton && ( - <EuiFlexItem grow={false}>{loadPrebuiltRulesAndTemplatesButton}</EuiFlexItem> - )} - {reloadPrebuiltRulesAndTemplatesButton && ( - <EuiFlexItem grow={false}>{reloadPrebuiltRulesAndTemplatesButton}</EuiFlexItem> - )} - <EuiFlexItem grow={false}> - <EuiToolTip position="top" content={i18n.UPLOAD_VALUE_LISTS_TOOLTIP}> - <EuiButton - data-test-subj="open-value-lists-modal-button" - iconType="importAction" - isDisabled={!canWriteListsIndex || !canUserCRUD || loading} - onClick={showValueListFlyout} - > - {i18n.IMPORT_VALUE_LISTS} - </EuiButton> - </EuiToolTip> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiButton - data-test-subj="rules-import-modal-button" - iconType="importAction" - isDisabled={!userHasPermissions(canUserCRUD) || loading} - onClick={showImportModal} - > - {i18n.IMPORT_RULE} - </EuiButton> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <SecuritySolutionLinkButton - data-test-subj="create-new-rule" - fill - iconType="plusInCircle" - isDisabled={!userHasPermissions(canUserCRUD) || loading} - deepLinkId={SecurityPageName.rulesCreate} - > - {i18n.ADD_NEW_RULE} - </SecuritySolutionLinkButton> - </EuiFlexItem> - </EuiFlexGroup> - </HeaderPage> - {(prePackagedRuleStatus === 'ruleNeedUpdate' || - prePackagedTimelineStatus === 'timelineNeedUpdate') && ( - <UpdatePrePackagedRulesCallOut - data-test-subj="update-callout-button" - loading={loadingCreatePrePackagedRules} - numberOfUpdatedRules={rulesNotUpdated ?? 0} - numberOfUpdatedTimelines={timelinesNotUpdated ?? 0} - updateRules={showMlJobUpgradeModal} - /> - )} - <AllRules - createPrePackagedRules={createPrePackagedRules} - data-test-subj="all-rules" - loadingCreatePrePackagedRules={loadingCreatePrePackagedRules} - hasPermissions={userHasPermissions(canUserCRUD)} - rulesCustomInstalled={rulesCustomInstalled} - rulesInstalled={rulesInstalled} - rulesNotInstalled={rulesNotInstalled} - rulesNotUpdated={rulesNotUpdated} - /> - </SecuritySolutionPageWrapper> - </RulesTableContextProvider> - - <SpyRoute pageName={SecurityPageName.rules} /> - </> - ); -}; - -export const RulesPage = React.memo(RulesPageComponent); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 8b32821bc71f1..b9948ca90578b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -484,13 +484,20 @@ export const EDIT_RULE_SETTINGS = i18n.translate( } ); -export const EDIT_RULE_SETTINGS_TOOLTIP = i18n.translate( - 'xpack.securitySolution.detectionEngine.rules.allRules.actions.editRuleSettingsToolTip', +export const LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.actions.lackOfKibanaActionsFeaturePrivileges', { defaultMessage: 'You do not have Kibana Actions privileges', } ); +export const LACK_OF_KIBANA_SECURITY_PRIVILEGES = i18n.translate( + 'xpack.securitySolution.detectionEngine.rules.allRules.actions.lackOfKibanaSecurityPrivileges', + { + defaultMessage: 'You do not have Kibana Security privileges', + } +); + export const DUPLICATE_RULE = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.allRules.actions.duplicateRuleDescription', { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index 950191acad3d9..81ebc4b24cf9c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -9,12 +9,12 @@ import type { List } from '@kbn/securitysolution-io-ts-list-types'; import type { RiskScoreMapping, + Severity, + SeverityMapping, ThreatIndex, ThreatMapping, Threats, Type, - SeverityMapping, - Severity, } from '@kbn/securitysolution-io-ts-alerting-types'; import type { DataViewBase, Filter } from '@kbn/es-query'; import type { RuleAction } from '@kbn/alerting-plugin/common'; @@ -25,16 +25,16 @@ import type { FieldValueQueryBar } from '../../../components/rules/query_bar'; import type { FieldValueTimeline } from '../../../components/rules/pick_timeline'; import type { FieldValueThreshold } from '../../../components/rules/threshold_input'; import type { - Author, BuildingBlockType, - License, RelatedIntegrationArray, RequiredFieldArray, + RuleAuthorArray, + RuleLicense, RuleNameOverride, - SortOrder, SetupGuide, TimestampOverride, -} from '../../../../../common/detection_engine/schemas/common'; +} from '../../../../../common/detection_engine/rule_schema'; +import type { SortOrder } from '../../../../../common/detection_engine/schemas/common'; import type { EqlOptionsSelected } from '../../../../../common/search_strategy'; import type { RuleResponseAction, @@ -215,12 +215,12 @@ export interface DefineStepRuleJson { } export interface AboutStepRuleJson { - author: Author; + author: RuleAuthorArray; building_block_type?: BuildingBlockType; exceptions_list?: List[]; name: string; description: string; - license: License; + license: RuleLicense; severity: string; severity_mapping: SeverityMapping; risk_score: number; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/use_get_saved_query.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/use_get_saved_query.ts index 5cd530754d8ce..fe113defb4eac 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/use_get_saved_query.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/use_get_saved_query.ts @@ -36,7 +36,10 @@ export const useGetSavedQuery = ( const query = useQuery( ['detectionEngine', 'rule', 'savedQuery', savedQueryId], async () => { - if (!savedQueryId) { + // load saved query only if rule type === 'saved_query', as other rule types still can have saved_id property that is not used + // Rule schema allows to save any rule with saved_id property, but it only used for saved_query rule type + // In future we might look in possibility to restrict rule schema (breaking change!) and remove saved_id from the rest of rules through migration + if (!savedQueryId || ruleType !== 'saved_query') { return null; } @@ -44,10 +47,6 @@ export const useGetSavedQuery = ( }, { onError: onError ?? defaultErrorHandler, - // load saved query only if rule type === 'saved_query', as other rule types still can have saved_id property that is not used - // Rule schema allows to save any rule with saved_id property, but it only used for saved_query rule type - // In future we might look in possibility to restrict rule schema (breaking change!) and remove saved_id from the rest of rules through migration - enabled: Boolean(savedQueryId) && ruleType === 'saved_query', retry: false, } ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts index 26f23bba9591d..5c04e749e481d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts @@ -20,7 +20,10 @@ import { DEFAULT_THREAT_MATCH_QUERY, RULES_PATH } from '../../../../../common/co import type { AboutStepRule, DefineStepRule, RuleStepsOrder, ScheduleStepRule } from './types'; import { DataSourceType, RuleStep } from './types'; import type { GetSecuritySolutionUrl } from '../../../../common/components/link_to'; -import { RuleDetailTabs, RULE_DETAILS_TAB_NAME } from './details'; +import { + RuleDetailTabs, + RULE_DETAILS_TAB_NAME, +} from '../../../../detection_engine/rule_details_ui/pages/rule_details'; import { fillEmptySeverityMappings } from './helpers'; export const ruleStepsOrder: RuleStepsOrder = [ diff --git a/x-pack/plugins/security_solution/public/exceptions/routes.tsx b/x-pack/plugins/security_solution/public/exceptions/routes.tsx index 512baa600e298..b977a98722444 100644 --- a/x-pack/plugins/security_solution/public/exceptions/routes.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/routes.tsx @@ -11,7 +11,7 @@ import { Route } from '@kbn/kibana-react-plugin/public'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import * as i18n from './translations'; import { EXCEPTIONS_PATH, SecurityPageName } from '../../common/constants'; -import { ExceptionListsTable } from '../detections/pages/detection_engine/rules/all/exceptions/exceptions_table'; +import { ExceptionListsTable } from '../detection_engine/rule_exceptions_ui/pages/exceptions/exceptions_table'; import { SpyRoute } from '../common/utils/route/spy_routes'; import { NotFoundPage } from '../app/404'; import { useReadonlyHeader } from '../use_readonly_header'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx index 27d8cc54fd0da..a3e062912070c 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx @@ -19,7 +19,6 @@ import { TestProviders, } from '../../../common/mock'; import { HostDetailsTabs } from './details_tabs'; -import type { HostDetailsTabsProps } from './types'; import { hostDetailsPagePath } from '../types'; import { type } from './utils'; import { useMountAppended } from '../../../common/utils/use_mount_appended'; @@ -114,10 +113,6 @@ describe('body', () => { }, }); - const componentProps: Record<string, Partial<HostDetailsTabsProps>> = { - events: { pageFilters: mockHostDetailsPageFilters }, - alerts: { pageFilters: mockHostDetailsPageFilters }, - }; const mount = useMountAppended(); Object.entries(scenariosMap).forEach(([path, componentName]) => @@ -133,7 +128,7 @@ describe('body', () => { indexNames={[]} indexPattern={mockIndexPattern} type={type} - pageFilters={mockHostDetailsPageFilters} + hostDetailsFilter={mockHostDetailsPageFilters} filterQuery={filterQuery} from={'2020-07-07T08:20:18.966Z'} to={'2020-07-08T08:20:18.966Z'} @@ -181,7 +176,7 @@ describe('body', () => { title: 'filebeat-*,auditbeat-*,packetbeat-*', }, hostName: 'host-1', - ...(componentProps[path] != null ? componentProps[path] : []), + ...(path === 'events' && { additionalFilters: mockHostDetailsPageFilters }), }); }) ); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx index e970195b6ffe5..8837f28c08200 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React from 'react'; import { Switch } from 'react-router-dom'; import { Route } from '@kbn/kibana-react-plugin/public'; @@ -16,7 +16,6 @@ import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anom import { useGlobalTime } from '../../../common/containers/use_global_time'; import { AnomaliesHostTable } from '../../../common/components/ml/tables/anomalies_host_table'; import { EventsQueryTabBody } from '../../../common/components/events_tab'; -import { hostNameExistsFilter } from '../../../common/components/visualization_actions/utils'; import type { HostDetailsTabsProps } from './types'; import { type } from './utils'; @@ -34,8 +33,8 @@ export const HostDetailsTabs = React.memo<HostDetailsTabsProps>( filterQuery, indexNames, indexPattern, - pageFilters = [], hostDetailsPagePath, + hostDetailsFilter, }) => { const { from, to, isInitializing, deleteQuery, setQuery } = useGlobalTime(); @@ -52,11 +51,6 @@ export const HostDetailsTabs = React.memo<HostDetailsTabsProps>( hostName: detailName, }; - const externalAlertPageFilters = useMemo( - () => [...hostNameExistsFilter, ...pageFilters], - [pageFilters] - ); - return ( <Switch> <Route path={`${hostDetailsPagePath}/:tabName(${HostsTableType.authentications})`}> @@ -71,10 +65,9 @@ export const HostDetailsTabs = React.memo<HostDetailsTabsProps>( <Route path={`${hostDetailsPagePath}/:tabName(${HostsTableType.events})`}> <EventsQueryTabBody - {...tabProps} - pageFilters={pageFilters} + additionalFilters={hostDetailsFilter} tableId={TableId.hostsPageEvents} - externalAlertPageFilters={externalAlertPageFilters} + {...tabProps} /> </Route> <Route path={`${hostDetailsPagePath}/:tabName(${HostsTableType.risk})`}> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index a601e29951656..9d430654c7748 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -80,7 +80,7 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta ); const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); const query = useDeepEqualSelector(getGlobalQuerySelector); - const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); + const globalFilters = useDeepEqualSelector(getGlobalFiltersQuerySelector); const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime(); const { globalFullScreen } = useGlobalFullScreen(); @@ -127,14 +127,14 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta buildEsQuery( indexPattern, [query], - [...hostDetailsPageFilters, ...filters], + [...hostDetailsPageFilters, ...globalFilters], getEsQueryConfig(uiSettings) ), ]; } catch (e) { return [undefined, e]; } - }, [filters, indexPattern, query, uiSettings, hostDetailsPageFilters]); + }, [globalFilters, indexPattern, query, uiSettings, hostDetailsPageFilters]); const stringifiedAdditionalFilters = JSON.stringify(rawFilteredQuery); useInvalidFilterQuery({ @@ -255,7 +255,7 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta indexNames={selectedPatterns} isInitializing={isInitializing} deleteQuery={deleteQuery} - pageFilters={hostDetailsPageFilters} + hostDetailsFilter={hostDetailsPageFilters} to={to} from={from} detailName={detailName} diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts index 5299037a2eb3b..67129bb9fe430 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/types.ts @@ -38,7 +38,7 @@ export type HostDetailsNavTab = Record<KeyHostDetailsNavTab, NavTab>; export type HostDetailsTabsProps = HostBodyComponentDispatchProps & HostsQueryProps & { indexNames: string[]; - pageFilters?: Filter[]; + hostDetailsFilter: Filter[]; filterQuery?: string; indexPattern: DataViewBase; type: hostsModel.HostsType; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index 432ebfdde99b8..b287c32147b54 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -81,7 +81,8 @@ const HostsComponent = () => { ); const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); const query = useDeepEqualSelector(getGlobalQuerySelector); - const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); + const globalFilters = useDeepEqualSelector(getGlobalFiltersQuerySelector); + const getHostRiskScoreFilterQuerySelector = useMemo( () => hostsSelectors.hostRiskScoreSeverityFilterSelector(), [] @@ -97,16 +98,17 @@ const HostsComponent = () => { const { tabName } = useParams<{ tabName: string }>(); const tabsFilters: Filter[] = React.useMemo(() => { if (tabName === HostsTableType.events) { - return filters.length > 0 ? [...filters, ...hostNameExistsFilter] : hostNameExistsFilter; + return [...globalFilters, ...hostNameExistsFilter]; } if (tabName === HostsTableType.risk) { const severityFilter = generateSeverityFilter(severitySelection, RiskScoreEntity.host); - - return [...severityFilter, ...hostNameExistsFilter, ...filters]; + return [...globalFilters, ...hostNameExistsFilter, ...severityFilter]; } - return filters; - }, [severitySelection, tabName, filters]); + + return globalFilters; + }, [globalFilters, severitySelection, tabName]); + const updateDateRange = useCallback<UpdateDateRange>( ({ x }) => { if (!x) { @@ -124,15 +126,15 @@ const HostsComponent = () => { [dispatch] ); const { indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); - const [filterQuery, kqlError] = useMemo( + const [globalFilterQuery, kqlError] = useMemo( () => convertToBuildEsQuery({ config: getEsQueryConfig(uiSettings), indexPattern, queries: [query], - filters, + filters: globalFilters, }), - [filters, indexPattern, uiSettings, query] + [globalFilters, indexPattern, uiSettings, query] ); const [tabsFilterQuery] = useMemo( () => @@ -145,7 +147,14 @@ const HostsComponent = () => { [indexPattern, query, tabsFilters, uiSettings] ); - useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to }); + useInvalidFilterQuery({ + id: ID, + filterQuery: globalFilterQuery, + kqlError, + query, + startDate: from, + endDate: to, + }); const isEnterprisePlus = useLicense().isEnterprise(); @@ -193,12 +202,12 @@ const HostsComponent = () => { /> <HostsKpiComponent - filterQuery={filterQuery} + filterQuery={globalFilterQuery} indexNames={selectedPatterns} from={from} setQuery={setQuery} to={to} - skip={isInitializing || !filterQuery} + skip={isInitializing || !!kqlError} updateDateRange={updateDateRange} /> @@ -218,13 +227,12 @@ const HostsComponent = () => { <HostsTabs deleteQuery={deleteQuery} to={to} - filterQuery={tabsFilterQuery || ''} + filterQuery={tabsFilterQuery} isInitializing={isInitializing} indexNames={selectedPatterns} setQuery={setQuery} from={from} type={hostsModel.HostsType.page} - pageFilters={tabsFilters} /> </SecuritySolutionPageWrapper> </StyledFullHeightContainer> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx index 208a5a062759a..18fd5628d2ebc 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useMemo } from 'react'; +import React from 'react'; import { Switch } from 'react-router-dom'; import { Route } from '@kbn/kibana-react-plugin/public'; @@ -25,18 +25,8 @@ import { import { TableId } from '../../../common/types'; import { hostNameExistsFilter } from '../../common/components/visualization_actions/utils'; -export const HostsTabs = memo<HostsTabsProps>( - ({ - deleteQuery, - filterQuery, - pageFilters = [], - from, - indexNames, - isInitializing, - setQuery, - to, - type, - }) => { +export const HostsTabs = React.memo<HostsTabsProps>( + ({ deleteQuery, filterQuery, from, indexNames, isInitializing, setQuery, to, type }) => { const tabProps = { deleteQuery, endDate: to, @@ -48,10 +38,6 @@ export const HostsTabs = memo<HostsTabsProps>( type, }; - const externalAlertPageFilters = useMemo( - () => [...hostNameExistsFilter, ...pageFilters], - [pageFilters] - ); return ( <Switch> <Route path={`${HOSTS_PATH}/:tabName(${HostsTableType.hosts})`}> @@ -68,10 +54,9 @@ export const HostsTabs = memo<HostsTabsProps>( </Route> <Route path={`${HOSTS_PATH}/:tabName(${HostsTableType.events})`}> <EventsQueryTabBody - {...tabProps} - pageFilters={pageFilters} + additionalFilters={hostNameExistsFilter} tableId={TableId.hostsPageEvents} - externalAlertPageFilters={externalAlertPageFilters} + {...tabProps} /> </Route> <Route path={`${HOSTS_PATH}/:tabName(${HostsTableType.sessions})`}> diff --git a/x-pack/plugins/security_solution/public/hosts/pages/types.ts b/x-pack/plugins/security_solution/public/hosts/pages/types.ts index e16ccfa100fb4..e04e61ddeac90 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/types.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/types.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { Filter } from '@kbn/es-query'; import type { hostsModel } from '../store'; import type { GlobalTimeArgs } from '../../common/containers/use_global_time'; import { HOSTS_PATH } from '../../../common/constants'; @@ -13,8 +12,7 @@ import { HOSTS_PATH } from '../../../common/constants'; export const hostDetailsPagePath = `${HOSTS_PATH}/name/:detailName`; export type HostsTabsProps = GlobalTimeArgs & { - filterQuery: string; - pageFilters?: Filter[]; + filterQuery?: string; indexNames: string[]; type: hostsModel.HostsType; }; diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_output.test.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_output.test.tsx index 55ab4a17ec5a5..255b4108a42e2 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_output.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_output.test.tsx @@ -42,7 +42,7 @@ describe('When using CommandExecutionOutput component', () => { }); it('should show long running hint message if pending and >15s have passed', () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); render(); expect(renderResult.queryByTestId('test-longRunningCommandHint')).toBeNull(); @@ -55,7 +55,7 @@ describe('When using CommandExecutionOutput component', () => { }); it('should remove long running hint message if command completes', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); render(); act(() => { diff --git a/x-pack/plugins/security_solution/public/management/components/console/service/parse_command_input.test.ts b/x-pack/plugins/security_solution/public/management/components/console/service/parse_command_input.test.ts index 1d0917d1a0959..a4d3a983041fd 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/service/parse_command_input.test.ts +++ b/x-pack/plugins/security_solution/public/management/components/console/service/parse_command_input.test.ts @@ -144,5 +144,25 @@ describe('when using parsed command input utils', () => { }) ); }); + + it.each([ + [String.raw`C:\Foo\Dir\whatever.jpg`, undefined], + [String.raw`C:\\abc`, undefined], + [String.raw`F:\foo\bar.docx`, undefined], + [String.raw`C:/foo/bar.docx`, undefined], + [String.raw`C:\\\//\/\\/\\\/abc/\/\/\///def.txt`, undefined], + [String.raw`C:\abc~!@#$%^&*()_'+`, undefined], + [String.raw`C:foobar`, undefined], + [String.raw`C:\dir with spaces\foo.txt`, undefined], + [String.raw`C:\dir\file with spaces.txt`, undefined], + [String.raw`/tmp/linux file with spaces "and quotes" omg.txt`, undefined], + ['c\\foo\\b\\-\\-ar.txt', String.raw`c\foo\b--ar.txt`], + ['c:\\foo\\b \\-\\-ar.txt', String.raw`c:\foo\b --ar.txt`], + ])('should preserve backslashes in argument values: %s', (path, expected) => { + const input = `foo --path "${path}"`; + const parsedCommand = parseCommandInput(input); + + expect(parsedCommand.args).toEqual({ path: [expected ?? path] }); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/console/service/parsed_command_input.ts b/x-pack/plugins/security_solution/public/management/components/console/service/parsed_command_input.ts index 76866f41955e3..78ab197ebd227 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/service/parsed_command_input.ts +++ b/x-pack/plugins/security_solution/public/management/components/console/service/parsed_command_input.ts @@ -67,7 +67,7 @@ const parseInputString = (rawInput: string): ParsedCommandInput => { let newArgValue = argNameAndValueTrimmedString .substring(firstSpaceOrEqualSign.index + 1) .trim() - .replace(/\\/g, ''); + .replace(/\\-\\-/g, '--'); if (newArgValue.charAt(0) === '"') { newArgValue = newArgValue.substring(1); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/endpoint_response_actions_console_commands.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/endpoint_response_actions_console_commands.ts index 0a5a32bbdfebc..5269306424a84 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/endpoint_response_actions_console_commands.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/endpoint_response_actions_console_commands.ts @@ -379,9 +379,10 @@ export const getEndpointResponseActionsConsoleCommands = ({ capabilities: endpointCapabilities, privileges: endpointPrivileges, }, - exampleUsage: 'get-file path="/full/path/to/file.txt"', + exampleUsage: 'get-file path "/full/path/to/file.txt" --comment "Possible malware"', exampleInstruction: ENTER_OR_ADD_COMMENT_ARG_INSTRUCTION, validate: capabilitiesAndPrivilegesValidator, + mustHaveArgs: true, args: { path: { required: true, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_file_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_file_action.tsx index 1f8cb4de72717..d0f090e6595c6 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_file_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_file_action.tsx @@ -5,11 +5,13 @@ * 2.0. */ -import { memo, useMemo } from 'react'; +import React, { memo, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; import { useSendGetFileRequest } from '../../hooks/endpoint/use_send_get_file_request'; import type { ResponseActionGetFileRequestBody } from '../../../../common/endpoint/schema/actions'; import { useConsoleActionSubmitter } from './hooks/use_console_action_submitter'; import type { ActionRequestComponentProps } from './types'; +import { ResponseActionFileDownloadLink } from '../response_action_file_download_link'; export const GetFileActionResult = memo< ActionRequestComponentProps<{ @@ -33,7 +35,7 @@ export const GetFileActionResult = memo< : undefined; }, [command.args.args, command.commandDefinition?.meta?.endpointId]); - return useConsoleActionSubmitter<ResponseActionGetFileRequestBody>({ + const { result, actionDetails } = useConsoleActionSubmitter<ResponseActionGetFileRequestBody>({ ResultComponent, setStore, store, @@ -42,8 +44,26 @@ export const GetFileActionResult = memo< actionCreator, actionRequestBody, dataTestSubj: 'getFile', - }).result; + pendingMessage: i18n.translate('xpack.securitySolution.getFileAction.pendingMessage', { + defaultMessage: 'Retrieving the file from host.', + }), + }); - // FIXME:PT implement success UI output once we have download API + if (actionDetails?.isCompleted && actionDetails.wasSuccessful) { + return ( + <ResultComponent + showAs="success" + data-test-subj="getFileSuccess" + title={i18n.translate( + 'xpack.securitySolution.endpointResponseActions.getFileAction.successTitle', + { defaultMessage: 'File retrieved from the host.' } + )} + > + <ResponseActionFileDownloadLink action={actionDetails} /> + </ResultComponent> + ); + } + + return result; }); GetFileActionResult.displayName = 'GetFileActionResult'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.tsx index 7183b5cc61ef7..98f8954f0dd65 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.tsx @@ -63,6 +63,11 @@ export interface UseConsoleActionSubmitterOptions< actionRequestBody: TReqBody | undefined; dataTestSubj?: string; + + /** */ + pendingMessage?: string; + + successMessage?: string; } /** @@ -78,6 +83,8 @@ export interface UseConsoleActionSubmitterOptions< * @param store * @param ResultComponent * @param dataTestSubj + * @param pendingMessage + * @param successMessage */ export const useConsoleActionSubmitter = < TReqBody extends BaseActionRequestBody = BaseActionRequestBody, @@ -91,6 +98,8 @@ export const useConsoleActionSubmitter = < store, ResultComponent, dataTestSubj, + pendingMessage, + successMessage, }: UseConsoleActionSubmitterOptions< TReqBody, TActionOutputContent @@ -237,7 +246,11 @@ export const useConsoleActionSubmitter = < // Calculate the action's UI result based on the different API responses const result = useMemo(() => { if (isPending) { - return <ResultComponent showAs="pending" data-test-subj={getTestId('pending')} />; + return ( + <ResultComponent showAs="pending" data-test-subj={getTestId('pending')}> + {pendingMessage} + </ResultComponent> + ); } const apiError = actionRequestError || actionDetailsError; @@ -246,7 +259,7 @@ export const useConsoleActionSubmitter = < return ( <ResultComponent showAs="failure" data-test-subj={getTestId('apiFailure')}> <FormattedMessage - id="xpack.securitySolution.endpointResponseActions.killProcess.performApiErrorMessage" + id="xpack.securitySolution.endpointResponseActions.actionSubmitter.apiErrorDetails" defaultMessage="The following error was encountered:" /> <FormattedError error={apiError} data-test-subj={getTestId('apiErrorDetails')} /> @@ -271,6 +284,7 @@ export const useConsoleActionSubmitter = < ResultComponent={ResultComponent} action={actionDetails} data-test-subj={getTestId('success')} + title={successMessage} /> ); } @@ -283,6 +297,8 @@ export const useConsoleActionSubmitter = < actionDetails, ResultComponent, getTestId, + pendingMessage, + successMessage, ]); return { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/integration_tests/get_file_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/integration_tests/get_file_action.test.tsx index 01b50dc759a8b..d69771446038f 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/integration_tests/get_file_action.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/integration_tests/get_file_action.test.tsx @@ -22,8 +22,11 @@ import { GET_FILE_ROUTE } from '../../../../../common/endpoint/constants'; import { getEndpointAuthzInitialStateMock } from '../../../../../common/endpoint/service/authz/mocks'; import type { EndpointPrivileges } from '../../../../../common/endpoint/types'; import { INSUFFICIENT_PRIVILEGES_FOR_COMMAND } from '../../../../common/translations'; +import type { HttpFetchOptionsWithPath } from '@kbn/core-http-browser'; -describe('When using get-file aciton from response actions console', () => { +jest.mock('../../../../common/components/user_privileges'); + +describe('When using get-file action from response actions console', () => { let render: ( capabilities?: EndpointCapabilities[] ) => Promise<ReturnType<AppContextTestRender['render']>>; @@ -124,4 +127,32 @@ describe('When using get-file aciton from response actions console', () => { 'Argument can only be used once: --comment' ); }); + + it('should display download link once action completes', async () => { + const actionDetailsApiResponseMock: ReturnType<typeof apiMocks.responseProvider.actionDetails> = + { + data: { + ...apiMocks.responseProvider.actionDetails({ + path: '/1', + } as HttpFetchOptionsWithPath).data, + + completedAt: new Date().toISOString(), + command: 'get-file', + }, + }; + apiMocks.responseProvider.actionDetails.mockReturnValue(actionDetailsApiResponseMock); + + await render(); + enterConsoleCommand(renderResult, 'get-file --path="one/two"'); + + await waitFor(() => { + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalled(); + }); + + await waitFor(() => { + expect(renderResult.getByTestId('getFileSuccess').textContent).toEqual( + 'File retrieved from the host.Click here to download(ZIP file passcode: elastic)' + ); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action.formatter.test.ts b/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action.formatter.test.ts new file mode 100644 index 0000000000000..dc5c443e15e2e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action.formatter.test.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 { FleetServerAgentComponentUnit } from '@kbn/fleet-plugin/common/types'; + +import { PackageActionFormatter, titles, descriptions } from './package_action_formatter'; +import { ENDPOINT_ERROR_CODES } from '../../../../common/endpoint/constants'; + +describe('PackageActionFormatter', () => { + it('correctly formats es connection error', () => { + const unit: FleetServerAgentComponentUnit = { + id: 'test-id', + type: 'input', + status: 'failed', + message: 'test message', + payload: { + error: { + code: ENDPOINT_ERROR_CODES.ES_CONNECTION_ERROR, + message: 'an error message', + }, + }, + }; + const docLinks = { es_connection: 'somedoclink' }; + const formatter = new PackageActionFormatter(unit, docLinks); + expect(formatter.key).toBe('es_connection'); + expect(formatter.title).toBe(titles.get('es_connection')); + expect(formatter.description).toBe(descriptions.get('es_connection')); + expect(formatter.linkUrl).toBe(docLinks.es_connection); + }); + + it('correct formats generic error', () => { + const unit: FleetServerAgentComponentUnit = { + id: 'test-id', + type: 'input', + status: 'failed', + message: 'test message', + }; + const docLinks = { es_connection: 'somedoclink' }; + const formatter = new PackageActionFormatter(unit, docLinks); + expect(formatter.key).toBe('policy_failure'); + expect(formatter.title).toBe(titles.get('policy_failure')); + expect(formatter.description).toBe(descriptions.get('policy_failure')); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts b/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts index ac4aebf65c496..9af6983ecfcdb 100644 --- a/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts +++ b/x-pack/plugins/security_solution/public/management/components/package_action_item/package_action_formatter.ts @@ -7,6 +7,10 @@ import { i18n } from '@kbn/i18n'; import type { DocLinks } from '@kbn/doc-links'; +import type { + FleetServerAgentComponentUnit, + FleetServerAgentComponentStatus, +} from '@kbn/fleet-plugin/common/types'; import { ENDPOINT_ERROR_CODES } from '../../../../common/endpoint/constants'; @@ -78,13 +82,12 @@ export class PackageActionFormatter { public linkText?: string; constructor( - code: number, - message: string, + unit: FleetServerAgentComponentUnit, private docLinks: DocLinks['securitySolution']['packageActionTroubleshooting'] ) { - this.key = this.getKeyFromErrorCode(code); + this.key = this.getKeyFromErrorCode(unit.payload?.error?.code, unit.status); this.title = titles.get(this.key) ?? this.key; - this.description = descriptions.get(this.key) || message; + this.description = descriptions.get(this.key) || unit.payload?.error?.message; this.linkText = linkTexts.get(this.key); } @@ -94,10 +97,13 @@ export class PackageActionFormatter { ]; } - private getKeyFromErrorCode(code: number): PackageActions { + private getKeyFromErrorCode( + code: number, + status: FleetServerAgentComponentStatus + ): PackageActions { if (code === ENDPOINT_ERROR_CODES.ES_CONNECTION_ERROR) { return 'es_connection'; - } else if (code === 124) { + } else if (status === 'failed') { return 'policy_failure'; } else { throw new Error(`Invalid error code ${code}`); diff --git a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.test.tsx b/x-pack/plugins/security_solution/public/management/components/policy_response/integration_tests/policy_response_wrapper.test.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.test.tsx rename to x-pack/plugins/security_solution/public/management/components/policy_response/integration_tests/policy_response_wrapper.test.tsx index 3c6d4f66d59cb..f23e3bb005fba 100644 --- a/x-pack/plugins/security_solution/public/management/components/policy_response/policy_response_wrapper.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/policy_response/integration_tests/policy_response_wrapper.test.tsx @@ -7,28 +7,26 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; -import type { AppContextTestRender } from '../../../common/mock/endpoint'; -import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; -import type { PolicyResponseWrapperProps } from './policy_response_wrapper'; -import { PolicyResponseWrapper } from './policy_response_wrapper'; -import { HostPolicyResponseActionStatus } from '../../../../common/search_strategy'; -import { useGetEndpointPolicyResponse } from '../../hooks/endpoint/use_get_endpoint_policy_response'; +import type { AppContextTestRender } from '../../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../../common/mock/endpoint'; +import type { PolicyResponseWrapperProps } from '../policy_response_wrapper'; +import { PolicyResponseWrapper } from '../policy_response_wrapper'; +import { HostPolicyResponseActionStatus } from '../../../../../common/search_strategy'; +import { useGetEndpointPolicyResponse } from '../../../hooks/endpoint/use_get_endpoint_policy_response'; import type { HostPolicyResponse, HostPolicyResponseAppliedAction, -} from '../../../../common/endpoint/types'; -import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; -import { useGetEndpointDetails } from '../../hooks'; +} from '../../../../../common/endpoint/types'; +import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; +import { useGetEndpointDetails } from '../../../hooks'; import { descriptions, LINUX_DEADLOCK_MESSAGE, policyResponseTitles, -} from './policy_response_friendly_names'; +} from '../policy_response_friendly_names'; -jest.setTimeout(10000); - -jest.mock('../../hooks/endpoint/use_get_endpoint_policy_response'); -jest.mock('../../hooks/endpoint/use_get_endpoint_details'); +jest.mock('../../../hooks/endpoint/use_get_endpoint_policy_response'); +jest.mock('../../../hooks/endpoint/use_get_endpoint_details'); describe('when on the policy response', () => { const docGenerator = new EndpointDocGenerator(); diff --git a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/index.ts b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/index.ts new file mode 100644 index 0000000000000..35a781976e565 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/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 { ResponseActionFileDownloadLink } from './response_action_file_download_link'; diff --git a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx new file mode 100644 index 0000000000000..c243687b67378 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.test.tsx @@ -0,0 +1,185 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { AppContextTestRender } from '../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import type { + ActionDetails, + ResponseActionGetFileOutputContent, + ResponseActionGetFileParameters, +} from '../../../../common/endpoint/types'; +import React from 'react'; +import type { ResponseActionFileDownloadLinkProps } from './response_action_file_download_link'; +import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator'; +import { + FILE_NO_LONGER_AVAILABLE_MESSAGE, + ResponseActionFileDownloadLink, +} from './response_action_file_download_link'; +import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; +import { useUserPrivileges as _useUserPrivileges } from '../../../common/components/user_privileges'; +import { getDeferred } from '../../mocks/utils'; +import { waitFor } from '@testing-library/react'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; + +jest.mock('../../../common/components/user_privileges'); + +describe('When using the `ResponseActionFileDownloadLink` component', () => { + const useUserPrivilegesMock = _useUserPrivileges as jest.Mock< + ReturnType<typeof _useUserPrivileges> + >; + + let render: () => ReturnType<AppContextTestRender['render']>; + let renderResult: ReturnType<AppContextTestRender['render']>; + let renderProps: ResponseActionFileDownloadLinkProps; + let apiMocks: ReturnType<typeof responseActionsHttpMocks>; + + beforeEach(() => { + const appTestContext = createAppRootMockRenderer(); + + apiMocks = responseActionsHttpMocks(appTestContext.coreStart.http); + + renderProps = { + action: new EndpointActionGenerator('seed').generateActionDetails< + ResponseActionGetFileOutputContent, + ResponseActionGetFileParameters + >({ command: 'get-file', completedAt: new Date().toISOString() }), + 'data-test-subj': 'test', + }; + + render = () => { + renderResult = appTestContext.render(<ResponseActionFileDownloadLink {...renderProps} />); + return renderResult; + }; + }); + + it('should show download button if file is available', () => { + render(); + + expect(renderResult.getByTestId('test-downloadButton')).not.toBeNull(); + expect(renderResult.getByTestId('test-passcodeMessage')).toHaveTextContent( + '(ZIP file passcode: elastic)' + ); + }); + + it('should display custom button label', () => { + renderProps.buttonTitle = 'hello'; + render(); + + expect(renderResult.getByTestId('test-downloadButton')).toHaveTextContent('hello'); + }); + + it('should show loading indicator while calling file info api', async () => { + const deferred = getDeferred(); + + apiMocks.responseProvider.fileInfo.mockDelay.mockReturnValue(deferred.promise); + (renderProps.action as ActionDetails).completedAt = '2021-04-15T16:08:47.449Z'; + + render(); + + expect(renderResult.getByTestId('test-loading')).not.toBeNull(); + + // Release the `file info` api + deferred.resolve(); + + await waitFor(() => { + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalledWith({ + path: '/api/endpoint/action/123/agent-a/file', + }); + }); + + expect(renderResult.getByTestId('test-downloadButton')).not.toBeNull(); + }); + + it('should show file no longer available message if status is DELETED', async () => { + const fileInfoApiResponseMock = apiMocks.responseProvider.fileInfo(); + + fileInfoApiResponseMock.data.status = 'DELETED'; + apiMocks.responseProvider.fileInfo.mockReturnValue(fileInfoApiResponseMock); + + (renderProps.action as ActionDetails).completedAt = '2021-04-15T16:08:47.449Z'; + + render(); + + await waitFor(() => { + expect(renderResult.getByTestId('test-fileNoLongerAvailable')).toHaveTextContent( + FILE_NO_LONGER_AVAILABLE_MESSAGE + ); + }); + }); + + it('should show file no longer available message if file info api returns 404', async () => { + const error = { message: 'not found', response: { status: 404 } } as IHttpFetchError; + + (renderProps.action as ActionDetails).completedAt = '2021-04-15T16:08:47.449Z'; + apiMocks.responseProvider.fileInfo.mockImplementation(() => { + throw error; + }); + + render(); + + await waitFor(() => { + expect(renderResult.getByTestId('test-fileNoLongerAvailable')).toHaveTextContent( + FILE_NO_LONGER_AVAILABLE_MESSAGE + ); + }); + }); + + it('should show file info API error if one was encountered', async () => { + const error = { message: 'server error', response: { status: 500 } } as IHttpFetchError; + + (renderProps.action as ActionDetails).completedAt = '2021-04-15T16:08:47.449Z'; + apiMocks.responseProvider.fileInfo.mockImplementation(() => { + throw error; + }); + + render(); + + await waitFor(() => { + expect(renderResult.getByTestId('test-apiError')).toHaveTextContent('server error'); + }); + }); + + it('should show nothing if user does not have authz', () => { + const privileges = useUserPrivilegesMock(); + + useUserPrivilegesMock.mockImplementationOnce(() => { + return { + ...privileges, + endpointPrivileges: { + ...privileges.endpointPrivileges, + canWriteFileOperations: false, + }, + }; + }); + + render(); + + expect(apiMocks.responseProvider.fileInfo).not.toHaveBeenCalled(); + expect(renderResult.container.children.length).toBe(0); + }); + + it('should show nothing if action is not complete', () => { + const action = renderProps.action as ActionDetails; + action.completedAt = undefined; + action.isCompleted = false; + + render(); + + expect(apiMocks.responseProvider.fileInfo).not.toHaveBeenCalled(); + expect(renderResult.container.children.length).toBe(0); + }); + + it('should show nothing if action was not successful', () => { + const action = renderProps.action as ActionDetails; + action.wasSuccessful = false; + + render(); + + expect(apiMocks.responseProvider.fileInfo).not.toHaveBeenCalled(); + expect(renderResult.container.children.length).toBe(0); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx new file mode 100644 index 0000000000000..e873a4ce253f7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/response_action_file_download_link/response_action_file_download_link.tsx @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CSSProperties } from 'react'; +import React, { memo, useMemo } from 'react'; +import { EuiButtonEmpty, EuiLoadingContent, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import { resolvePathVariables } from '../../../common/utils/resolve_path_variables'; +import { FormattedError } from '../formatted_error'; +import { useGetFileInfo } from '../../hooks/endpoint/use_get_file_info'; +import { useUserPrivileges } from '../../../common/components/user_privileges'; +import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; +import type { MaybeImmutable } from '../../../../common/endpoint/types'; +import type { ActionDetails } from '../../../../common/endpoint/types/actions'; +import { ACTION_AGENT_FILE_DOWNLOAD_ROUTE } from '../../../../common/endpoint/constants'; + +const STYLE_INHERIT_FONT_FAMILY = Object.freeze<CSSProperties>({ + fontFamily: 'inherit', +}); + +const DEFAULT_BUTTON_TITLE = i18n.translate( + 'xpack.securitySolution.responseActionFileDownloadLink.downloadButtonLabel', + { defaultMessage: 'Click here to download' } +); + +export const FILE_NO_LONGER_AVAILABLE_MESSAGE = i18n.translate( + 'xpack.securitySolution.responseActionFileDownloadLink.fileNoLongerAvailable', + { defaultMessage: 'File is no longer available for download.' } +); + +export interface ResponseActionFileDownloadLinkProps { + action: MaybeImmutable<ActionDetails>; + /** If left undefined, the first agent that the action was sent to will be used */ + agentId?: string; + buttonTitle?: string; + 'data-test-subj'?: string; +} + +/** + * Displays the download link for a file retrieved via a Response Action. The download link + * button will only be displayed if the user has authorization to use file operations. + * + * NOTE: Currently displays only the link for the first host in the Action + */ +export const ResponseActionFileDownloadLink = memo<ResponseActionFileDownloadLinkProps>( + ({ action, agentId, buttonTitle = DEFAULT_BUTTON_TITLE, 'data-test-subj': dataTestSubj }) => { + const getTestId = useTestIdGenerator(dataTestSubj); + const { canWriteFileOperations } = useUserPrivileges().endpointPrivileges; + + // We don't need to call the file info API every time, especially if this component is used from the + // console, where the link is displayed within a short time. So we only do the API call if the + // action was completed more than 2 days ago. + const checkIfStillAvailable = useMemo(() => { + return ( + action.isCompleted && action.wasSuccessful && moment().diff(action.completedAt, 'days') > 2 + ); + }, [action.completedAt, action.isCompleted, action.wasSuccessful]); + + const downloadUrl = useMemo(() => { + return resolvePathVariables(ACTION_AGENT_FILE_DOWNLOAD_ROUTE, { + action_id: action.id, + agent_id: agentId ?? action.agents[0], + }); + }, [action.agents, action.id, agentId]); + + const { + isFetching, + data: fileInfo, + error, + } = useGetFileInfo(action, undefined, { + enabled: canWriteFileOperations && checkIfStillAvailable, + }); + + if (!canWriteFileOperations || !action.isCompleted || !action.wasSuccessful) { + return null; + } + + if (isFetching) { + return <EuiLoadingContent lines={1} data-test-subj={getTestId('loading')} />; + } + + // Check if file is no longer available + if ((error && error?.response?.status === 404) || fileInfo?.data.status === 'DELETED') { + return ( + <EuiText size="s" data-test-subj={getTestId('fileNoLongerAvailable')}> + {FILE_NO_LONGER_AVAILABLE_MESSAGE} + </EuiText> + ); + } else if (error) { + return <FormattedError error={error} data-test-subj={getTestId('apiError')} />; + } + + return ( + <> + <EuiButtonEmpty + href={downloadUrl} + iconType="download" + data-test-subj={getTestId('downloadButton')} + flush="left" + style={STYLE_INHERIT_FONT_FAMILY} + download + > + <EuiText size="s">{buttonTitle}</EuiText> + </EuiButtonEmpty> + <EuiText + size="s" + className="eui-displayInline" + data-test-subj={getTestId('passcodeMessage')} + > + <FormattedMessage + id="xpack.securitySolution.responseActionFileDownloadLink.passcodeInfo" + defaultMessage="(ZIP file passcode: {passcode})" + values={{ + passcode: 'elastic', + }} + /> + </EuiText> + </> + ); + } +); +ResponseActionFileDownloadLink.displayName = 'ResponseActionFileDownloadLink'; diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_file_info.test.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_file_info.test.ts new file mode 100644 index 0000000000000..bef446eca27b3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_file_info.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { AppContextTestRender, ReactQueryHookRenderer } from '../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; +import { useGetFileInfo } from './use_get_file_info'; +import { resolvePathVariables } from '../../../common/utils/resolve_path_variables'; +import { ACTION_AGENT_FILE_INFO_ROUTE } from '../../../../common/endpoint/constants'; +import type { + ActionDetails, + ResponseActionGetFileOutputContent, + ResponseActionGetFileParameters, +} from '../../../../common/endpoint/types'; +import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator'; +import { useQuery as _useQuery } from '@tanstack/react-query'; + +const useQueryMock = _useQuery as jest.Mock; + +jest.mock('@tanstack/react-query', () => { + const actualReactQueryModule = jest.requireActual('@tanstack/react-query'); + + return { + ...actualReactQueryModule, + useQuery: jest.fn((...args) => actualReactQueryModule.useQuery(...args)), + }; +}); + +describe('When using the `useGetFileInfo()` hook', () => { + let renderReactQueryHook: ReactQueryHookRenderer< + Parameters<typeof useGetFileInfo>, + ReturnType<typeof useGetFileInfo> + >; + let http: AppContextTestRender['coreStart']['http']; + let apiMocks: ReturnType<typeof responseActionsHttpMocks>; + let actionDetailsMock: ActionDetails; + + beforeEach(() => { + const testContext = createAppRootMockRenderer(); + + renderReactQueryHook = testContext.renderReactQueryHook as typeof renderReactQueryHook; + http = testContext.coreStart.http; + + apiMocks = responseActionsHttpMocks(http); + + actionDetailsMock = new EndpointActionGenerator('seed').generateActionDetails< + ResponseActionGetFileOutputContent, + ResponseActionGetFileParameters + >({ + command: 'get-file', + }); + }); + + it('should call the correct API', async () => { + await renderReactQueryHook(() => useGetFileInfo(actionDetailsMock)); + + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalledWith({ + path: resolvePathVariables(ACTION_AGENT_FILE_INFO_ROUTE, { + action_id: '123', + agent_id: 'agent-a', + }), + }); + }); + + it('should allow specific agent id to be set on input', async () => { + await renderReactQueryHook(() => useGetFileInfo(actionDetailsMock, 'abc')); + + expect(apiMocks.responseProvider.fileInfo).toHaveBeenCalledWith({ + path: resolvePathVariables(ACTION_AGENT_FILE_INFO_ROUTE, { + action_id: '123', + agent_id: 'abc', + }), + }); + }); + + it('should allow custom options ot be used', async () => { + await renderReactQueryHook(() => + useGetFileInfo(actionDetailsMock, undefined, { + queryKey: ['a', 'b'], + enabled: true, + retry: false, + }) + ); + + expect(useQueryMock).toHaveBeenCalledWith( + expect.objectContaining({ + queryKey: ['a', 'b'], + enabled: true, + retry: false, + }) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_file_info.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_file_info.ts new file mode 100644 index 0000000000000..c4c2905bfd1f8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_file_info.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { useQuery } from '@tanstack/react-query'; +import { resolvePathVariables } from '../../../common/utils/resolve_path_variables'; +import { useHttp } from '../../../common/lib/kibana/hooks'; +import type { + ActionDetails, + ActionFileInfoApiResponse, + MaybeImmutable, +} from '../../../../common/endpoint/types'; +import { ACTION_AGENT_FILE_INFO_ROUTE } from '../../../../common/endpoint/constants'; + +/** + * Retrieves information about a file that was uploaded by the endpoint as a result of a `get-file` action + * @param action + * @param [agentId] If left undefined, the first agent that the action was sent to will be used + * @param [options] + */ +export const useGetFileInfo = ( + action: MaybeImmutable<ActionDetails>, + agentId?: string, + options: UseQueryOptions<ActionFileInfoApiResponse, IHttpFetchError> = {} +): UseQueryResult<ActionFileInfoApiResponse, IHttpFetchError> => { + const http = useHttp(); + + return useQuery<ActionFileInfoApiResponse, IHttpFetchError>({ + queryKey: ['get-action-file-info', action.id, agentId ?? action.agents[0]], + ...options, + queryFn: () => { + const apiUrl = resolvePathVariables(ACTION_AGENT_FILE_INFO_ROUTE, { + action_id: action.id, + agent_id: agentId ?? action.agents[0], + }); + + return http.get<ActionFileInfoApiResponse>(apiUrl); + }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/management/mocks/response_actions_http_mocks.ts b/x-pack/plugins/security_solution/public/management/mocks/response_actions_http_mocks.ts index bc23427631fb9..52aa80b35586b 100644 --- a/x-pack/plugins/security_solution/public/management/mocks/response_actions_http_mocks.ts +++ b/x-pack/plugins/security_solution/public/management/mocks/response_actions_http_mocks.ts @@ -17,6 +17,7 @@ import { KILL_PROCESS_ROUTE, SUSPEND_PROCESS_ROUTE, GET_FILE_ROUTE, + ACTION_AGENT_FILE_INFO_ROUTE, } from '../../../common/endpoint/constants'; import type { ResponseProvidersInterface } from '../../common/mock/endpoint/http_handler_mock_factory'; import { httpHandlerMockFactory } from '../../common/mock/endpoint/http_handler_mock_factory'; @@ -29,6 +30,7 @@ import type { GetProcessesActionOutputContent, ResponseActionGetFileOutputContent, ResponseActionGetFileParameters, + ActionFileInfoApiResponse, } from '../../../common/endpoint/types'; export type ResponseActionsHttpMocksInterface = ResponseProvidersInterface<{ @@ -49,6 +51,8 @@ export type ResponseActionsHttpMocksInterface = ResponseProvidersInterface<{ processes: () => ActionDetailsApiResponse<GetProcessesActionOutputContent>; getFile: () => ActionDetailsApiResponse<ResponseActionGetFileOutputContent>; + + fileInfo: () => ActionFileInfoApiResponse; }>; export const responseActionsHttpMocks = httpHandlerMockFactory<ResponseActionsHttpMocksInterface>([ @@ -175,4 +179,21 @@ export const responseActionsHttpMocks = httpHandlerMockFactory<ResponseActionsHt return { data: response }; }, }, + { + id: 'fileInfo', + path: ACTION_AGENT_FILE_INFO_ROUTE, + method: 'get', + handler: (): ActionFileInfoApiResponse => { + return { + data: { + created: '2022-10-10T14:57:30.682Z', + id: '123', + mimeType: 'text/plain', + name: 'test.txt', + size: 1234, + status: 'READY', + }, + }; + }, + }, ]); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_generic_errors_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_generic_errors_list.tsx index cac9c2a7d0c2c..babb4c78af147 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_generic_errors_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_generic_errors_list.tsx @@ -23,11 +23,10 @@ export const EndpointGenericErrorsList = memo<PackageGenericErrorsListProps>( const globalEndpointErrors = useMemo(() => { const errors: PackageActionFormatter[] = []; packageErrors.forEach((unit) => { - if (unit.payload && unit.payload.error) { + if (unit.status === 'failed') { errors.push( new PackageActionFormatter( - unit.payload.error.code, - unit.payload.error.message, + unit, docLinks.links.securitySolution.packageActionTroubleshooting ) ); diff --git a/x-pack/plugins/security_solution/public/network/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/network/pages/details/details_tabs.tsx index 4985d7e896a61..560fc65583301 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/details_tabs.tsx @@ -5,14 +5,13 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React from 'react'; import { Switch } from 'react-router-dom'; import { EuiFlexItem, EuiSpacer } from '@elastic/eui'; import type { DataViewBase, Filter } from '@kbn/es-query'; import { Route } from '@kbn/kibana-react-plugin/public'; import { TableId } from '../../../../common/types'; -import { getNetworkDetailsPageFilter } from '../../../common/components/visualization_actions/utils'; import { AnomaliesNetworkTable } from '../../../common/components/ml/tables/anomalies_network_table'; import { FlowTargetSourceDest } from '../../../../common/search_strategy/security_solution/network'; import { EventsQueryTabBody } from '../../../common/components/events_tab/events_query_tab_body'; @@ -43,21 +42,17 @@ interface NetworkDetailTabsProps { setQuery: GlobalTimeArgs['setQuery']; indexPattern: DataViewBase; flowTarget: FlowTargetSourceDest; + networkDetailsFilter: Filter[]; } export const NetworkDetailsTabs = React.memo<NetworkDetailTabsProps>( - ({ indexPattern, flowTarget, ...rest }) => { + ({ flowTarget, indexPattern, networkDetailsFilter, ...rest }) => { const type = networkModel.NetworkType.details; const commonProps = { ...rest, type }; const flowTabProps = { ...commonProps, indexPattern }; const commonPropsWithFlowTarget = { ...commonProps, flowTarget }; - const networkDetailsPageFilters: Filter[] = useMemo( - () => getNetworkDetailsPageFilter(rest.ip), - [rest.ip] - ); - return ( <Switch> <Route @@ -116,9 +111,9 @@ export const NetworkDetailsTabs = React.memo<NetworkDetailTabsProps>( path={`${NETWORK_DETAILS_PAGE_PATH}/:flowTarget/:tabName(${NetworkDetailsRouteType.events})`} > <EventsQueryTabBody - pageFilters={networkDetailsPageFilters} - tableId={TableId.networkPageEvents} + additionalFilters={networkDetailsFilter} {...commonProps} + tableId={TableId.networkPageEvents} /> </Route> </Switch> diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx index c9327fe261c10..63eb9a1cb60c0 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx @@ -76,7 +76,7 @@ const NetworkDetailsComponent: React.FC = () => { const canReadAlerts = hasKibanaREAD && hasIndexRead; const query = useDeepEqualSelector(getGlobalQuerySelector); - const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); + const globalFilters = useDeepEqualSelector(getGlobalFiltersQuerySelector); const type = networkModel.NetworkType.details; const narrowDateRange = useCallback( @@ -101,7 +101,9 @@ const NetworkDetailsComponent: React.FC = () => { }, [detailName, dispatch]); const { indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); + const ip = decodeIpv6(detailName); + const networkDetailsFilter = useMemo(() => getNetworkDetailsPageFilter(ip), [ip]); const [rawFilteredQuery, kqlError] = useMemo(() => { try { @@ -109,14 +111,14 @@ const NetworkDetailsComponent: React.FC = () => { buildEsQuery( indexPattern, [query], - [...getNetworkDetailsPageFilter(ip), ...filters], + [...networkDetailsFilter, ...globalFilters], getEsQueryConfig(uiSettings) ), ]; } catch (e) { return [undefined, e]; } - }, [filters, indexPattern, ip, query, uiSettings]); + }, [globalFilters, indexPattern, networkDetailsFilter, query, uiSettings]); const stringifiedAdditionalFilters = JSON.stringify(rawFilteredQuery); useInvalidFilterQuery({ @@ -150,12 +152,6 @@ const NetworkDetailsComponent: React.FC = () => { [flowTarget, ip] ); - // When the filterQuery comes back as undefined, it means an error has been thrown and the request should be skipped - const shouldSkip = useMemo( - () => isInitializing || rawFilteredQuery === undefined, - [isInitializing, rawFilteredQuery] - ); - const entityFilter = useMemo( () => ({ field: `${flowTarget}.ip`, @@ -243,10 +239,11 @@ const NetworkDetailsComponent: React.FC = () => { startDate={from} filterQuery={stringifiedAdditionalFilters} indexNames={selectedPatterns} - skip={shouldSkip} + skip={isInitializing || !!kqlError} setQuery={setQuery} indexPattern={indexPattern} flowTarget={flowTarget} + networkDetailsFilter={networkDetailsFilter} /> </SecuritySolutionPageWrapper> </> diff --git a/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx b/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx index 6f64628baabb9..b336fe1cf2a7f 100644 --- a/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/navigation/network_routes.tsx @@ -21,7 +21,7 @@ import { } from '.'; import { EventsQueryTabBody } from '../../../common/components/events_tab'; import { AnomaliesNetworkTable } from '../../../common/components/ml/tables/anomalies_network_table'; -import { filterNetworkExternalAlertData } from '../../../common/components/visualization_actions/utils'; +import { sourceOrDestinationIpExistsFilter } from '../../../common/components/visualization_actions/utils'; import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anomalies_query_tab_body'; import { TableId } from '../../../../common/types'; import { ConditionalFlexGroup } from './conditional_flex_group'; @@ -115,7 +115,7 @@ export const NetworkRoutes = React.memo<NetworkRoutesProps>( </Route> <Route path={`${NETWORK_PATH}/:tabName(${NetworkRouteType.events})`}> <EventsQueryTabBody - pageFilters={filterNetworkExternalAlertData} + additionalFilters={sourceOrDestinationIpExistsFilter} tableId={TableId.networkPageEvents} {...tabProps} /> diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index b9db632e3fd74..b15f6603ee52f 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -49,7 +49,7 @@ import { TableId } from '../../../common/types/timeline'; import { useSourcererDataView } from '../../common/containers/sourcerer'; import { useDeepEqualSelector, useShallowEqualSelector } from '../../common/hooks/use_selector'; import { useInvalidFilterQuery } from '../../common/hooks/use_invalid_filter_query'; -import { filterNetworkExternalAlertData } from '../../common/components/visualization_actions/utils'; +import { sourceOrDestinationIpExistsFilter } from '../../common/components/visualization_actions/utils'; import { LandingPageComponent } from '../../common/components/landing_page'; import { dataTableSelectors } from '../../common/store/data_table'; import { tableDefaults } from '../../common/store/data_table/defaults'; @@ -78,7 +78,7 @@ const NetworkComponent = React.memo<NetworkComponentProps>( ); const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); const query = useDeepEqualSelector(getGlobalQuerySelector); - const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); + const globalFilters = useDeepEqualSelector(getGlobalFiltersQuerySelector); const { to, from, setQuery, isInitializing } = useGlobalTime(); const { globalFullScreen } = useGlobalFullScreen(); @@ -89,12 +89,10 @@ const NetworkComponent = React.memo<NetworkComponentProps>( const tabsFilters = useMemo(() => { if (tabName === NetworkRouteType.events) { - return filters.length > 0 - ? [...filters, ...filterNetworkExternalAlertData] - : filterNetworkExternalAlertData; + return [...globalFilters, ...sourceOrDestinationIpExistsFilter]; } - return filters; - }, [tabName, filters]); + return globalFilters; + }, [tabName, globalFilters]); const updateDateRange = useCallback<UpdateDateRange>( ({ x }) => { @@ -143,8 +141,9 @@ const NetworkComponent = React.memo<NetworkComponentProps>( config: getEsQueryConfig(kibana.services.uiSettings), indexPattern, queries: [query], - filters, + filters: globalFilters, }); + const [tabsFilterQuery] = convertToBuildEsQuery({ config: getEsQueryConfig(kibana.services.uiSettings), indexPattern, @@ -185,7 +184,7 @@ const NetworkComponent = React.memo<NetworkComponentProps>( > <EmbeddedMap query={query} - filters={filters} + filters={globalFilters} startDate={from} endDate={to} setQuery={setQuery} diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx index cc727efa5188e..465072743d1d5 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/columns.tsx @@ -7,22 +7,25 @@ import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiLink } from '@elastic/eui'; -import { ML_PAGES, useMlHref } from '@kbn/ml-plugin/public'; +import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; import { useDispatch } from 'react-redux'; + import * as i18n from './translations'; import type { AnomaliesCount } from '../../../../common/components/ml/anomaly/use_anomalies_search'; -import { - AnomalyJobStatus, - AnomalyEntity, -} from '../../../../common/components/ml/anomaly/use_anomalies_search'; -import { useKibana } from '../../../../common/lib/kibana'; +import { AnomalyEntity } from '../../../../common/components/ml/anomaly/use_anomalies_search'; + import { LinkAnchor, SecuritySolutionLinkAnchor } from '../../../../common/components/links'; import { SecurityPageName } from '../../../../app/types'; import { usersActions } from '../../../../users/store'; import { hostsActions } from '../../../../hosts/store'; import { HostsType } from '../../../../hosts/store/model'; import { UsersType } from '../../../../users/store/model'; +import type { SecurityJob } from '../../../../common/components/ml_popover/types'; +import { + isJobFailed, + isJobStarted, + isJobLoading, +} from '../../../../../common/machine_learning/helpers'; type AnomaliesColumns = Array<EuiBasicTableColumn<AnomaliesCount>>; @@ -30,10 +33,10 @@ const MediumShadeText = styled.span` color: ${({ theme }) => theme.eui.euiColorMediumShade}; `; -const INSTALL_JOBS_DOC = - 'https://www.elastic.co/guide/en/machine-learning/current/ml-ad-run-jobs.html'; - -export const useAnomaliesColumns = (loading: boolean): AnomaliesColumns => { +export const useAnomaliesColumns = ( + loading: boolean, + onJobStateChange: (job: SecurityJob) => Promise<void> +): AnomaliesColumns => { const columns: AnomaliesColumns = useMemo( () => [ { @@ -42,8 +45,8 @@ export const useAnomaliesColumns = (loading: boolean): AnomaliesColumns => { truncateText: true, mobileOptions: { show: true }, 'data-test-subj': 'anomalies-table-column-name', - render: (name, { status, count }) => { - if (count > 0 || status === AnomalyJobStatus.enabled) { + render: (name, { count, job }) => { + if (count > 0 || (job && isJobStarted(job.jobState, job.datafeedState))) { return name; } else { return <MediumShadeText>{name}</MediumShadeText>; @@ -52,14 +55,16 @@ export const useAnomaliesColumns = (loading: boolean): AnomaliesColumns => { }, { field: 'count', - sortable: ({ count, status }) => { + sortable: ({ count, job }) => { if (count > 0) { return count; } - if (status === AnomalyJobStatus.disabled) { - return -1; + + if (job && isJobStarted(job.jobState, job.datafeedState)) { + return 0; } - return -2; + + return -1; }, truncateText: true, align: 'right', @@ -67,65 +72,41 @@ export const useAnomaliesColumns = (loading: boolean): AnomaliesColumns => { mobileOptions: { show: true }, width: '15%', 'data-test-subj': 'anomalies-table-column-count', - render: (count, { status, jobId, entity }) => { - if (loading) return ''; - - if (count > 0 || status === AnomalyJobStatus.enabled) { - return <AnomaliesTabLink count={count} jobId={jobId} entity={entity} />; + render: (count, { entity, job }) => { + if (!job) return ''; + + if (count > 0 || isJobStarted(job.jobState, job.datafeedState)) { + return <AnomaliesTabLink count={count} jobId={job.id} entity={entity} />; + } else if (isJobFailed(job.jobState, job.datafeedState)) { + return i18n.JOB_STATUS_FAILED; + } else if (job.isCompatible) { + return <EnableJob job={job} isLoading={loading} onJobStateChange={onJobStateChange} />; } else { - if (status === AnomalyJobStatus.disabled && jobId) { - return <EnableJobLink jobId={jobId} />; - } - - if (status === AnomalyJobStatus.uninstalled) { - return ( - <EuiLink external target={'_blank'} href={INSTALL_JOBS_DOC}> - {i18n.JOB_STATUS_UNINSTALLED} - </EuiLink> - ); - } - - return <MediumShadeText>{I18N_JOB_STATUS[status]}</MediumShadeText>; + return <EuiIcon aria-label="Warning" size="s" type="alert" color="warning" />; } }, }, ], - [loading] + [loading, onJobStateChange] ); return columns; }; -const I18N_JOB_STATUS = { - [AnomalyJobStatus.disabled]: i18n.JOB_STATUS_DISABLED, - [AnomalyJobStatus.failed]: i18n.JOB_STATUS_FAILED, -}; - -const EnableJobLink = ({ jobId }: { jobId: string }) => { - const { - services: { - ml, - http, - application: { navigateToUrl }, - }, - } = useKibana(); - - const jobUrl = useMlHref(ml, http.basePath.get(), { - page: ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, - pageState: { - jobId, - }, - }); - - const onClick = useCallback( - (ev) => { - ev.preventDefault(); - navigateToUrl(jobUrl); - }, - [jobUrl, navigateToUrl] - ); +const EnableJob = ({ + job, + isLoading, + onJobStateChange, +}: { + job: SecurityJob; + isLoading: boolean; + onJobStateChange: (job: SecurityJob) => Promise<void>; +}) => { + const handleChange = useCallback(() => onJobStateChange(job), [job, onJobStateChange]); - return ( - <LinkAnchor data-test-subj="jobs-table-link" href={jobUrl} onClick={onClick}> + return isLoading || isJobLoading(job.jobState, job.datafeedState) ? ( + <EuiLoadingSpinner size="m" data-test-subj="job-switch-loader" /> + ) : ( + <LinkAnchor onClick={handleChange} data-test-subj="enable-job"> {i18n.RUN_JOB} </LinkAnchor> ); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx index 642e4ae207362..b47741a219107 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.test.tsx @@ -9,12 +9,10 @@ import { render } from '@testing-library/react'; import React from 'react'; import { EntityAnalyticsAnomalies } from '.'; import type { AnomaliesCount } from '../../../../common/components/ml/anomaly/use_anomalies_search'; -import { - AnomalyJobStatus, - AnomalyEntity, -} from '../../../../common/components/ml/anomaly/use_anomalies_search'; +import { AnomalyEntity } from '../../../../common/components/ml/anomaly/use_anomalies_search'; import { TestProviders } from '../../../../common/mock'; +import type { SecurityJob } from '../../../../common/components/ml_popover/types'; const mockUseNotableAnomaliesSearch = jest.fn().mockReturnValue({ isLoading: false, @@ -22,6 +20,15 @@ const mockUseNotableAnomaliesSearch = jest.fn().mockReturnValue({ refetch: jest.fn(), }); +jest.mock( + '@kbn/ml-plugin/public/application/components/jobs_awaiting_node_warning/new_job_awaiting_node_shared/lazy_loader', + () => { + return { + MLJobsAwaitingNodeWarning: () => <></>, + }; + } +); + jest.mock('../../../../common/components/ml/anomaly/use_anomalies_search', () => { const original = jest.requireActual( '../../../../common/components/ml/anomaly/use_anomalies_search' @@ -66,10 +73,9 @@ describe('EntityAnalyticsAnomalies', () => { it('renders enabled jobs', () => { const jobCount: AnomaliesCount = { - jobId: 'v3_windows_anomalous_script', + job: { isInstalled: true, datafeedState: 'started', jobState: 'opened' } as SecurityJob, name: 'v3_windows_anomalous_script', count: 9999, - status: AnomalyJobStatus.enabled, entity: AnomalyEntity.User, }; @@ -93,10 +99,14 @@ describe('EntityAnalyticsAnomalies', () => { it('renders disabled jobs', () => { const jobCount: AnomaliesCount = { - jobId: 'v3_windows_anomalous_script', + job: { + isInstalled: true, + datafeedState: 'stopped', + jobState: 'closed', + isCompatible: true, + } as SecurityJob, name: 'v3_windows_anomalous_script', count: 0, - status: AnomalyJobStatus.disabled, entity: AnomalyEntity.User, }; @@ -114,15 +124,15 @@ describe('EntityAnalyticsAnomalies', () => { expect(getByTestId('anomalies-table-column-name')).toHaveTextContent(jobCount.name); expect(getByTestId('anomalies-table-column-count')).toHaveTextContent('Run job'); - expect(getByTestId('jobs-table-link')).toBeInTheDocument(); + expect(getByTestId('enable-job')).toBeInTheDocument(); }); it('renders uninstalled jobs', () => { const jobCount: AnomaliesCount = { - jobId: 'v3_windows_anomalous_script', + job: { isInstalled: false, isCompatible: true } as SecurityJob, name: 'v3_windows_anomalous_script', count: 0, - status: AnomalyJobStatus.uninstalled, + entity: AnomalyEntity.User, }; @@ -139,15 +149,20 @@ describe('EntityAnalyticsAnomalies', () => { ); expect(getByTestId('anomalies-table-column-name')).toHaveTextContent(jobCount.name); - expect(getByTestId('anomalies-table-column-count')).toHaveTextContent('uninstalled'); + expect(getByTestId('anomalies-table-column-count')).toHaveTextContent('Run job'); + expect(getByTestId('enable-job')).toBeInTheDocument(); }); it('renders failed jobs', () => { const jobCount: AnomaliesCount = { - jobId: 'v3_windows_anomalous_script', + job: { + isInstalled: true, + datafeedState: 'failed', + jobState: 'failed', + isCompatible: true, + } as SecurityJob, name: 'v3_windows_anomalous_script', count: 0, - status: AnomalyJobStatus.failed, entity: AnomalyEntity.User, }; @@ -169,10 +184,9 @@ describe('EntityAnalyticsAnomalies', () => { it('renders empty count column while loading', () => { const jobCount: AnomaliesCount = { - jobId: 'v3_windows_anomalous_script', + job: undefined, name: 'v3_windows_anomalous_script', count: 0, - status: AnomalyJobStatus.failed, entity: AnomalyEntity.User, }; @@ -190,4 +204,32 @@ describe('EntityAnalyticsAnomalies', () => { expect(getByTestId('anomalies-table-column-count').textContent).toEqual('Count'); // 'Count' is always rendered by only displayed on mobile }); + + it('renders a warning message when jobs are incompatible', () => { + const jobCount: AnomaliesCount = { + job: { + isInstalled: true, + datafeedState: 'started', + jobState: 'opened', + isCompatible: false, + } as SecurityJob, + name: 'v3_windows_anomalous_script', + count: 0, + entity: AnomalyEntity.User, + }; + + mockUseNotableAnomaliesSearch.mockReturnValue({ + isLoading: false, + data: [jobCount], + refetch: jest.fn(), + }); + + const { getByTestId } = render( + <TestProviders> + <EntityAnalyticsAnomalies /> + </TestProviders> + ); + + expect(getByTestId('incompatible_jobs_warnings')).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx index d88ad343eab3c..c3333a3e13957 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/index.tsx @@ -4,10 +4,18 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useEffect, useMemo, useState } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, EuiPanel } from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiPanel, + EuiSpacer, +} from '@elastic/eui'; -import { ML_PAGES, useMlHref } from '@kbn/ml-plugin/public'; +import { MLJobsAwaitingNodeWarning, ML_PAGES, useMlHref } from '@kbn/ml-plugin/public'; +import { FormattedMessage } from '@kbn/i18n-react'; import { HeaderSection } from '../../../../common/components/header_section'; import { useQueryToggle } from '../../../../common/containers/query_toggle'; import { LastUpdatedAt } from '../../../../common/components/last_updated_at'; @@ -27,6 +35,8 @@ import { SecurityPageName } from '../../../../app/types'; import { getTabsOnUsersUrl } from '../../../../common/components/link_to/redirect_to_users'; import { UsersTableType } from '../../../../users/store/model'; import { useKibana } from '../../../../common/lib/kibana'; +import { useEnableDataFeed } from '../../../../common/components/ml_popover/hooks/use_enable_data_feed'; +import type { SecurityJob } from '../../../../common/components/ml_popover/types'; const TABLE_QUERY_ID = 'entityAnalyticsDashboardAnomaliesTable'; @@ -41,7 +51,7 @@ export const ENTITY_ANALYTICS_ANOMALIES_PANEL = 'entity_analytics_anomalies'; export const EntityAnalyticsAnomalies = () => { const { - services: { ml, http }, + services: { ml, http, docLinks }, } = useKibana(); const jobsUrl = useMlHref(ml, http.basePath.get(), { @@ -51,22 +61,40 @@ export const EntityAnalyticsAnomalies = () => { const [updatedAt, setUpdatedAt] = useState<number>(Date.now()); const { toggleStatus, setToggleStatus } = useQueryToggle(TABLE_QUERY_ID); const { deleteQuery, setQuery, from, to } = useGlobalTime(false); - const { isLoading, data, refetch } = useNotableAnomaliesSearch({ + const { + isLoading: isSearchLoading, + data, + refetch, + } = useNotableAnomaliesSearch({ skip: !toggleStatus, from, to, }); - const columns = useAnomaliesColumns(isLoading); + const { isLoading: isEnableDataFeedLoading, enableDatafeed } = useEnableDataFeed(); + + const handleJobStateChange = useCallback( + async (job: SecurityJob) => { + const result = await enableDatafeed(job, job.latestTimestampMs || 0, true); + refetch(); + return result; + }, + [refetch, enableDatafeed] + ); + + const columns = useAnomaliesColumns( + isSearchLoading || isEnableDataFeedLoading, + handleJobStateChange + ); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); useEffect(() => { setUpdatedAt(Date.now()); - }, [isLoading]); // Update the time when data loads + }, [isSearchLoading]); // Update the time when data loads useQueryInspector({ refetch, queryId: TABLE_QUERY_ID, - loading: isLoading, + loading: isSearchLoading, setQuery, deleteQuery, }); @@ -87,12 +115,22 @@ export const EntityAnalyticsAnomalies = () => { return [onClick, href]; }, [getSecuritySolutionLinkProps]); + const installedJobsIds = useMemo( + () => data.filter(({ job }) => !!job && job.isInstalled).map(({ job }) => job?.id ?? ''), + [data] + ); + + const incompatibleJobCount = useMemo( + () => data.filter(({ job }) => job && !job.isCompatible).length, + [data] + ); + return ( <EuiPanel hasBorder data-test-subj={ENTITY_ANALYTICS_ANOMALIES_PANEL}> <HeaderSection title={i18n.ANOMALIES_TITLE} titleSize="s" - subtitle={<LastUpdatedAt isUpdating={isLoading} updatedAt={updatedAt} />} + subtitle={<LastUpdatedAt isUpdating={isSearchLoading} updatedAt={updatedAt} />} toggleStatus={toggleStatus} toggleQuery={setToggleStatus} > @@ -124,12 +162,41 @@ export const EntityAnalyticsAnomalies = () => { </EuiFlexItem> </EuiFlexGroup> </HeaderSection> + + {incompatibleJobCount > 0 && ( + <> + <EuiCallOut + title={i18n.MODULE_NOT_COMPATIBLE_TITLE(incompatibleJobCount)} + data-test-subj="incompatible_jobs_warnings" + color="warning" + iconType="alert" + size="s" + > + <p> + <FormattedMessage + defaultMessage="We could not find any data, see {mlDocs} for more information on Machine Learning job requirements." + id="xpack.securitySolution.components.mlPopup.moduleNotCompatibleDescription" + values={{ + mlDocs: ( + <a href={`${docLinks.links.siem.ml}`} rel="noopener noreferrer" target="_blank"> + {i18n.ANOMALY_DETECTION_DOCS} + </a> + ), + }} + /> + </p> + </EuiCallOut> + + <EuiSpacer size="m" /> + </> + )} + <MLJobsAwaitingNodeWarning jobIds={installedJobsIds} /> {toggleStatus && ( <EuiInMemoryTable responsive={false} items={data} columns={columns} - loading={isLoading} + loading={isSearchLoading} id={TABLE_QUERY_ID} sorting={TABLE_SORTING} /> diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts index 59d5a5ce37244..fdef8b65baddf 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/anomalies/translations.ts @@ -76,3 +76,17 @@ export const JOB_STATUS_FAILED = i18n.translate( defaultMessage: 'failed', } ); + +export const MODULE_NOT_COMPATIBLE_TITLE = (incompatibleJobCount: number) => + i18n.translate('xpack.securitySolution.entityAnalytics.anomalies.moduleNotCompatibleTitle', { + values: { incompatibleJobCount }, + defaultMessage: + '{incompatibleJobCount} {incompatibleJobCount, plural, =1 {job is} other {jobs are}} currently unavailable', + }); + +export const ANOMALY_DETECTION_DOCS = i18n.translate( + 'xpack.securitySolution.entityAnalytics.anomalies.AnomalyDetectionDocsTitle', + { + defaultMessage: 'Anomaly Detection with Machine Learning', + } +); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx index 1daf91cd2285c..78ded5e6fc941 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/header/index.tsx @@ -30,6 +30,7 @@ import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; import { useQueryInspector } from '../../../../common/components/page/manage_query'; import { ENTITY_ANALYTICS_ANOMALIES_PANEL } from '../anomalies'; +import { isJobStarted } from '../../../../../common/machine_learning/helpers'; const StyledEuiTitle = styled(EuiTitle)` color: ${({ theme: { eui } }) => eui.euiColorDanger}; @@ -69,6 +70,7 @@ export const EntityAnalyticsHeader = () => { }); const { data } = useNotableAnomaliesSearch({ skip: false, from, to }); + const dispatch = useDispatch(); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; @@ -138,8 +140,14 @@ export const EntityAnalyticsHeader = () => { inspect: inspectHostRiskScore, }); - // Anomalies are enabled if at least one job is installed - const areJobsEnabled = useMemo(() => data.some(({ jobId }) => !!jobId), [data]); + // Anomaly jobs are enabled if at least one job is started or has data + const areJobsEnabled = useMemo( + () => + data.some( + ({ job, count }) => count > 0 || (job && isJobStarted(job.jobState, job.datafeedState)) + ), + [data] + ); const totalAnomalies = useMemo( () => (areJobsEnabled ? sumBy('count', data) : '-'), diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx index 055a172e54b73..a19168b5e864b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/columns.tsx @@ -7,19 +7,28 @@ import React from 'react'; import type { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiIcon, EuiToolTip } from '@elastic/eui'; +import { EuiLink, EuiIcon, EuiToolTip } from '@elastic/eui'; +import { get } from 'lodash/fp'; import { UsersTableType } from '../../../../users/store/model'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import { HostDetailsLink, UserDetailsLink } from '../../../../common/components/links'; import { HostsTableType } from '../../../../hosts/store/model'; import { RiskScore } from '../../../../common/components/severity/common'; -import type { HostRiskScore, RiskSeverity } from '../../../../../common/search_strategy'; +import type { + HostRiskScore, + RiskSeverity, + UserRiskScore, +} from '../../../../../common/search_strategy'; import { RiskScoreEntity, RiskScoreFields } from '../../../../../common/search_strategy'; import * as i18n from './translations'; +import { FormattedCount } from '../../../../common/components/formatted_number'; -type HostRiskScoreColumns = Array<EuiBasicTableColumn<HostRiskScore>>; +type HostRiskScoreColumns = Array<EuiBasicTableColumn<HostRiskScore & UserRiskScore>>; -export const getRiskScoreColumns = (riskEntity: RiskScoreEntity): HostRiskScoreColumns => [ +export const getRiskScoreColumns = ( + riskEntity: RiskScoreEntity, + openEntityInTimeline: (entityName: string) => void +): HostRiskScoreColumns => [ { field: riskEntity === RiskScoreEntity.host ? 'host.name' : 'user.name', name: i18n.ENTITY_NAME(riskEntity), @@ -75,4 +84,20 @@ export const getRiskScoreColumns = (riskEntity: RiskScoreEntity): HostRiskScoreC return getEmptyTagValue(); }, }, + { + field: RiskScoreFields.alertsCount, + width: '15%', + name: i18n.ALERTS, + truncateText: false, + mobileOptions: { show: true }, + render: (alertCount: number, risk) => ( + <EuiLink + data-test-subj="risk-score-alerts" + disabled={alertCount === 0} + onClick={() => openEntityInTimeline(get('host.name', risk) ?? get('user.name', risk))} + > + <FormattedCount count={alertCount} /> + </EuiLink> + ), + }, ]; diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx index 89011082a2f81..70faf174ae32f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.test.tsx @@ -9,6 +9,7 @@ import { render } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../../../common/mock'; import { EntityAnalyticsRiskScores } from '.'; +import type { UserRiskScore } from '../../../../../common/search_strategy'; import { RiskScoreEntity, RiskSeverity } from '../../../../../common/search_strategy'; import type { SeverityCount } from '../../../../common/components/severity/types'; import { useRiskScore, useRiskScoreKpi } from '../../../../risk_score/containers'; @@ -116,5 +117,38 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])( expect(queryByTestId('entity_analytics_content')).not.toBeInTheDocument(); }); + + it('renders alerts count', () => { + mockUseQueryToggle.mockReturnValue({ toggleStatus: true, setToggleStatus: jest.fn() }); + mockUseRiskScoreKpi.mockReturnValue({ + severityCount: mockSeverityCount, + loading: false, + }); + const alertsCount = 999; + const data: UserRiskScore[] = [ + { + '@timestamp': '1234567899', + user: { + name: 'testUsermame', + risk: { + rule_risks: [], + calculated_level: RiskSeverity.high, + calculated_score_norm: 75, + multipliers: [], + }, + }, + alertsCount, + }, + ]; + mockUseRiskScore.mockReturnValue({ ...defaultProps, data }); + + const { queryByTestId } = render( + <TestProviders> + <EntityAnalyticsRiskScores riskEntity={riskEntity} /> + </TestProviders> + ); + + expect(queryByTestId('risk-score-alerts')).toHaveTextContent(alertsCount.toString()); + }); } ); diff --git a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx index 066c4f29d2787..13899e88f38f9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/entity_analytics/risk_score/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useDispatch } from 'react-redux'; @@ -40,6 +40,7 @@ import { Loader } from '../../../../common/components/loader'; import { Panel } from '../../../../common/components/panel'; import * as commonI18n from '../common/translations'; import { usersActions } from '../../../../users/store'; +import { useNavigateToTimeline } from '../../detection_response/hooks/use_navigate_to_timeline'; const HOST_RISK_TABLE_QUERY_ID = 'hostRiskDashboardTable'; const HOST_RISK_KPI_QUERY_ID = 'headerHostRiskScoreKpiQuery'; @@ -90,8 +91,24 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc [dispatch, riskEntity] ); + const { openHostInTimeline, openUserInTimeline } = useNavigateToTimeline(); + + const openEntityInTimeline = useCallback( + (entityName: string) => { + if (riskEntity === RiskScoreEntity.host) { + openHostInTimeline({ hostName: entityName }); + } else if (riskEntity === RiskScoreEntity.user) { + openUserInTimeline({ userName: entityName }); + } + }, + [riskEntity, openHostInTimeline, openUserInTimeline] + ); + const { toggleStatus, setToggleStatus } = useQueryToggle(entity.tableQueryId); - const columns = useMemo(() => getRiskScoreColumns(riskEntity), [riskEntity]); + const columns = useMemo( + () => getRiskScoreColumns(riskEntity, openEntityInTimeline), + [riskEntity, openEntityInTimeline] + ); const [selectedSeverity, setSelectedSeverity] = useState<RiskSeverity[]>([]); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); @@ -146,6 +163,7 @@ const EntityAnalyticsRiskScoresComponent = ({ riskEntity }: { riskEntity: RiskSc }, timerange, riskEntity, + includeAlertsCount: true, }); useQueryInspector({ diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx index e4706e40aa470..762d805ecb61b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.test.tsx @@ -49,7 +49,7 @@ describe('EventCounts', () => { (wrapper.find('Memo(OverviewNetworkComponent)').first().props() as OverviewNetworkProps) .filterQuery ).toContain( - '{"bool":{"filter":[{"bool":{"should":[{"bool":{"should":[{"exists":{"field":"source.ip"}}],"minimum_should_match":1}},{"bool":{"should":[{"exists":{"field":"destination.ip"}}],"minimum_should_match":1}}],"minimum_should_match":1}}]}}]' + '{"bool":{"must":[],"filter":[{"bool":{"should":[{"exists":{"field":"source.ip"}},{"exists":{"field":"destination.ip"}}],"minimum_should_match":1}}],"should":[],"must_not":[]}}' ); }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx index 5194d697c96b1..f61ee1bf1fb1c 100644 --- a/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/event_counts/index.tsx @@ -19,7 +19,7 @@ import type { GlobalTimeArgs } from '../../../common/containers/use_global_time' import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query'; import { hostNameExistsFilter, - filterNetworkExternalAlertData, + sourceOrDestinationIpExistsFilter, } from '../../../common/components/visualization_actions/utils'; interface Props extends Pick<GlobalTimeArgs, 'from' | 'to' | 'setQuery'> { @@ -57,7 +57,7 @@ const EventCountsComponent: React.FC<Props> = ({ config: getEsQueryConfig(uiSettings), indexPattern, queries: [query], - filters: [...filters, ...filterNetworkExternalAlertData], + filters: [...filters, ...sourceOrDestinationIpExistsFilter], }), [filters, indexPattern, uiSettings, query] ); diff --git a/x-pack/plugins/security_solution/public/resolver/test_utilities/extend_jest.ts b/x-pack/plugins/security_solution/public/resolver/test_utilities/extend_jest.ts index 7bb7180712c99..64977ffd5c065 100644 --- a/x-pack/plugins/security_solution/public/resolver/test_utilities/extend_jest.ts +++ b/x-pack/plugins/security_solution/public/resolver/test_utilities/extend_jest.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { MatcherHintOptions } from 'jest-matcher-utils'; + /** * Typescript won't allow global namespace stuff unless you're in a module. * This wouldn't otherwise be a module. The code runs as soon as it's imported. @@ -36,7 +38,7 @@ expect.extend({ ): Promise<{ pass: boolean; message: () => string }> { // Used in printing out the pass or fail message const matcherName = 'toYieldEqualTo'; - const options: jest.MatcherHintOptions = { + const options: MatcherHintOptions = { comment: 'deep equality with any yielded value', isNot: this.isNot, promise: this.promise, @@ -102,7 +104,7 @@ expect.extend({ ): Promise<{ pass: boolean; message: () => string }> { // Used in printing out the pass or fail message const matcherName = 'toYieldObjectEqualTo'; - const options: jest.MatcherHintOptions = { + const options: MatcherHintOptions = { comment: 'subset equality with any yielded value', isNot: this.isNot, promise: this.promise, diff --git a/x-pack/plugins/security_solution/public/risk_score/components/translations.ts b/x-pack/plugins/security_solution/public/risk_score/components/translations.ts index 2016dd1d57990..8d845c6d99987 100644 --- a/x-pack/plugins/security_solution/public/risk_score/components/translations.ts +++ b/x-pack/plugins/security_solution/public/risk_score/components/translations.ts @@ -50,3 +50,7 @@ export const getRiskEntityTranslation = ( return riskEntity === RiskScoreEntity.host ? HOST : USER; }; + +export const ALERTS = i18n.translate('xpack.securitySolution.riskScore.overview.alerts', { + defaultMessage: 'Alerts', +}); diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx index c9a8f448c352d..dc2b8dac58493 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.test.tsx @@ -168,6 +168,8 @@ describe.each([RiskScoreEntity.host, RiskScoreEntity.user])( expect(mockSearch).toHaveBeenCalledWith({ defaultIndex: [`ml_${riskEntity}_risk_score_latest_default`], factoryQueryType: `${riskEntity}sRiskScore`, + riskScoreEntity: riskEntity, + includeAlertsCount: false, }); }); diff --git a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx index b700f83f7f702..977bc10caecc5 100644 --- a/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx +++ b/x-pack/plugins/security_solution/public/risk_score/containers/all/index.tsx @@ -45,6 +45,7 @@ export interface RiskScoreState<T extends RiskScoreEntity.host | RiskScoreEntity export interface UseRiskScoreParams { filterQuery?: ESQuery | string; onlyLatest?: boolean; + includeAlertsCount?: boolean; pagination?: | { cursorStart: number; @@ -76,6 +77,7 @@ export const useRiskScore = <T extends RiskScoreEntity.host | RiskScoreEntity.us skip = false, pagination, riskEntity, + includeAlertsCount = false, }: UseRiskScore<T>): RiskScoreState<T> => { const spaceId = useSpaceId(); const defaultIndex = spaceId @@ -158,6 +160,8 @@ export const useRiskScore = <T extends RiskScoreEntity.host | RiskScoreEntity.us ? { defaultIndex: [defaultIndex], factoryQueryType, + riskScoreEntity: riskEntity, + includeAlertsCount, filterQuery: createFilter(filterQuery), pagination: cursorStart !== undefined && querySize !== undefined @@ -179,6 +183,8 @@ export const useRiskScore = <T extends RiskScoreEntity.host | RiskScoreEntity.us sort, requestTimerange, onlyLatest, + riskEntity, + includeAlertsCount, ] ); diff --git a/x-pack/plugins/security_solution/public/rules/routes.tsx b/x-pack/plugins/security_solution/public/rules/routes.tsx index 633b4005110af..5cfaae23907d2 100644 --- a/x-pack/plugins/security_solution/public/rules/routes.tsx +++ b/x-pack/plugins/security_solution/public/rules/routes.tsx @@ -12,13 +12,13 @@ import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import * as i18n from './translations'; import { RULES_PATH, SecurityPageName } from '../../common/constants'; import { NotFoundPage } from '../app/404'; -import { RulesPage } from '../detections/pages/detection_engine/rules'; -import { CreateRulePage } from '../detections/pages/detection_engine/rules/create'; +import { RulesPage } from '../detection_engine/rule_management_ui/pages/rule_management'; +import { CreateRulePage } from '../detection_engine/rule_creation_ui/pages/rule_creation'; import { RuleDetailsPage, RuleDetailTabs, -} from '../detections/pages/detection_engine/rules/details'; -import { EditRulePage } from '../detections/pages/detection_engine/rules/edit'; +} from '../detection_engine/rule_details_ui/pages/rule_details'; +import { EditRulePage } from '../detection_engine/rule_creation_ui/pages/rule_editing'; import { useReadonlyHeader } from '../use_readonly_header'; import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper'; import { SpyRoute } from '../common/utils/route/spy_routes'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/flyout/__snapshots__/index.test.tsx.snap index df6539a20c74e..94d8b5146b961 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/__snapshots__/index.test.tsx.snap @@ -109,7 +109,7 @@ exports[`Flyout rendering it renders correctly against snapshot 1`] = ` class="euiFlexItem euiFlexItem--flexGrowZero" > <span - color="success" + color="warning" data-euiicon-type="dot" /> </div> diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.test.tsx index 59579924e3d88..0195e4d5c62f6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/pane/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import React from 'react'; import { TestProviders } from '../../../../common/mock'; @@ -15,6 +15,15 @@ import { useGetUserCasesPermissions } from '../../../../common/lib/kibana'; jest.mock('../../../../common/lib/kibana'); const originalKibanaLib = jest.requireActual('../../../../common/lib/kibana'); +jest.mock('@kbn/i18n-react', () => { + const originalModule = jest.requireActual('@kbn/i18n-react'); + const FormattedRelative = jest.fn().mockImplementation(() => '20 hours ago'); + + return { + ...originalModule, + FormattedRelative, + }; +}); // Restore the useGetUserCasesPermissions so the calling functions can receive a valid permissions object // The returned permissions object will indicate that the user does not have permissions by default @@ -30,21 +39,25 @@ jest.mock('../../../../common/hooks/use_resolve_conflict', () => { }); describe('Pane', () => { - test('renders with display block by default', () => { + test('renders with display block by default', async () => { const EmptyComponent = render( <TestProviders> <Pane timelineId={TimelineId.test} /> </TestProviders> ); - expect(EmptyComponent.getByTestId('flyout-pane')).toHaveStyle('display: block'); + await waitFor(() => { + expect(EmptyComponent.getByTestId('flyout-pane')).toHaveStyle('display: block'); + }); }); - test('renders with display none when visibility is set to false', () => { + test('renders with display none when visibility is set to false', async () => { const EmptyComponent = render( <TestProviders> <Pane timelineId={TimelineId.test} visible={false} /> </TestProviders> ); - expect(EmptyComponent.getByTestId('flyout-pane')).toHaveStyle('display: none'); + await waitFor(() => { + expect(EmptyComponent.getByTestId('flyout-pane')).toHaveStyle('display: none'); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.test.tsx index cd90f1cfb8e7e..a851c90405994 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.test.tsx @@ -15,7 +15,7 @@ import type { ColumnHeaderOptions, HeaderActionProps, } from '../../../../../../common/types/timeline'; -import { TimelineTabs } from '../../../../../../common/types/timeline'; +import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; import { timelineActions } from '../../../../store/timeline'; import { getColumnHeader } from '../column_headers/helpers'; @@ -35,7 +35,7 @@ jest.mock('../../../../../common/hooks/use_selector', () => ({ })); const columnId = 'test-field'; -const timelineId = 'test-timeline'; +const timelineId = TimelineId.test; /* eslint-disable jsx-a11y/click-events-have-key-events */ mockTriggersActionsUi.getFieldBrowser.mockImplementation( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx index 08484bdb34c95..5807a9667942f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx @@ -7,7 +7,7 @@ import { mount } from 'enzyme'; import React from 'react'; -import { TableId } from '../../../../../../common/types/timeline'; +import { TableId, TimelineId } from '../../../../../../common/types/timeline'; import { TestProviders, mockTimelineModel, mockTimelineData } from '../../../../../common/mock'; import { Actions, isAlert } from '.'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; @@ -97,7 +97,7 @@ const defaultProps = { setEventsLoading: () => {}, showCheckboxes: true, showNotes: false, - timelineId: 'test', + timelineId: TimelineId.test, toggleShowNotes: () => {}, }; @@ -271,7 +271,7 @@ describe('Actions', () => { <Actions {...defaultProps} ecsData={ecsData} - timelineId={TableId.kubernetesPageSessions} + timelineId={TableId.kubernetesPageSessions} // not a bug, this needs to be fixed by providing a generic interface for actions registry /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx index 227327fd8e856..e2be31e9d5dd6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx @@ -19,6 +19,7 @@ import { HeaderComponent } from '.'; import { getNewSortDirectionOnClick, getNextSortDirection, getSortDirection } from './helpers'; import { Direction } from '../../../../../../../common/search_strategy'; import { useDeepEqualSelector } from '../../../../../../common/hooks/use_selector'; +import { TimelineId } from '../../../../../../../common/types'; const mockDispatch = jest.fn(); jest.mock('react-redux', () => { @@ -48,7 +49,7 @@ describe('Header', () => { sortDirection: Direction.desc, }, ]; - const timelineId = 'test'; + const timelineId = TimelineId.test; beforeEach(() => { (useDeepEqualSelector as jest.Mock).mockReturnValue({ isLoading: false }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index cffa38e3435b5..32251df6318f2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -18,7 +18,6 @@ import { kibanaObservable, mockGlobalState, mockTimelineData, - mockTimelineModel, SUB_PLUGINS_REDUCER, } from '../../../../common/mock'; import { TestProviders } from '../../../../common/mock/test_providers'; @@ -29,13 +28,14 @@ import type { Props } from '.'; import { StatefulBody } from '.'; import type { Sort } from './sort'; import { getDefaultControlColumn } from './control_columns'; -import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { timelineActions } from '../../../store/timeline'; import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline'; import { defaultRowRenderers } from './renderers'; import type { State } from '../../../../common/store'; import { createStore } from '../../../../common/store'; import { tGridReducer } from '@kbn/timelines-plugin/public'; +import { mount } from 'enzyme'; +import type { UseFieldBrowserOptionsProps } from '../../fields_browser'; jest.mock('../../../../common/hooks/use_app_toasts'); jest.mock('../../../../common/components/user_privileges', () => { @@ -49,6 +49,11 @@ jest.mock('../../../../common/components/user_privileges', () => { }; }); +const mockUseFieldBrowserOptions = jest.fn(); +jest.mock('../../fields_browser', () => ({ + useFieldBrowserOptions: (props: UseFieldBrowserOptionsProps) => mockUseFieldBrowserOptions(props), +})); + jest.mock('../../../../common/lib/kibana', () => { const originalModule = jest.requireActual('../../../../common/lib/kibana'); const mockCasesContract = jest.requireActual('@kbn/cases-plugin/public/mocks'); @@ -67,6 +72,7 @@ jest.mock('../../../../common/lib/kibana', () => { data: { search: jest.fn(), query: jest.fn(), + dataViews: jest.fn(), }, uiSettings: { get: jest.fn(), @@ -108,11 +114,6 @@ jest.mock('react-redux', () => { }; }); -jest.mock('../../../../common/hooks/use_selector', () => ({ - useShallowEqualSelector: () => mockTimelineModel, - useDeepEqualSelector: () => mockTimelineModel, -})); - jest.mock('../../../../common/components/link_to'); // Prevent Resolver from rendering @@ -129,9 +130,61 @@ jest.mock('../../fields_browser/create_field_button', () => ({ useCreateFieldButton: () => <></>, })); -// SKIP: https://github.com/elastic/kibana/issues/143718 -describe.skip('Body', () => { - const mount = useMountAppended(); +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + EuiScreenReaderOnly: () => <></>, + }; +}); +jest.mock('suricata-sid-db', () => { + return { + db: [], + }; +}); +jest.mock('react-beautiful-dnd', () => { + const original = jest.requireActual('react-beautiful-dnd'); + return { + ...original, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Droppable: ({ children }: { children: any }) => + children( + { + draggableProps: { + style: {}, + }, + innerRef: jest.fn(), + }, + {} + ), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Draggable: ({ children }: { children: any }) => + children( + { + draggableProps: { + style: {}, + }, + innerRef: jest.fn(), + }, + {} + ), + DraggableProvided: () => <></>, + DraggableStateSnapshot: () => <></>, + DraggingStyle: () => <></>, + NotDraggingStyle: () => <></>, + }; +}); + +describe('Body', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const getWrapper = async (childrenComponent: JSX.Element, store?: any) => { + const wrapper = mount(childrenComponent, { + wrappingComponent: TestProviders, + wrappingComponentProps: store ?? {}, + }); + await waitFor(() => wrapper.find('[data-test-subj="suricataRefs"]').exists()); + return wrapper; + }; const mockRefetch = jest.fn(); let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>; @@ -145,8 +198,8 @@ describe.skip('Body', () => { const props: Props = { activePage: 0, browserFields: mockBrowserFields, - data: mockTimelineData, - id: 'timeline-test', + data: [mockTimelineData[0]], + id: TimelineId.test, refetch: mockRefetch, renderCellValue: DefaultCellRenderer, rowRenderers: defaultRowRenderers, @@ -162,58 +215,58 @@ describe.skip('Body', () => { mockDispatch.mockClear(); }); - test('it renders the column headers', () => { - const wrapper = mount( - <TestProviders> - <StatefulBody {...props} /> - </TestProviders> - ); - + test('it renders the column headers', async () => { + const wrapper = await getWrapper(<StatefulBody {...props} />); expect(wrapper.find('[data-test-subj="column-headers"]').first().exists()).toEqual(true); }); - test('it renders the scroll container', () => { - const wrapper = mount( - <TestProviders> - <StatefulBody {...props} /> - </TestProviders> - ); - + test('it renders the scroll container', async () => { + const wrapper = await getWrapper(<StatefulBody {...props} />); expect(wrapper.find('[data-test-subj="timeline-body"]').first().exists()).toEqual(true); }); - test('it renders events', () => { - const wrapper = mount( - <TestProviders> - <StatefulBody {...props} /> - </TestProviders> - ); - + test('it renders events', async () => { + const wrapper = await getWrapper(<StatefulBody {...props} />); expect(wrapper.find('[data-test-subj="events"]').first().exists()).toEqual(true); }); test('it renders a tooltip for timestamp', async () => { + const { storage } = createSecuritySolutionStorageMock(); const headersJustTimestamp = defaultHeaders.filter((h) => h.id === '@timestamp'); - const testProps = { ...props, columnHeaders: headersJustTimestamp }; - const wrapper = mount( - <TestProviders> - <StatefulBody {...testProps} /> - </TestProviders> + const state: State = { + ...mockGlobalState, + timeline: { + ...mockGlobalState.timeline, + timelineById: { + ...mockGlobalState.timeline.timelineById, + [TimelineId.test]: { + ...mockGlobalState.timeline.timelineById[TimelineId.test], + id: TimelineId.test, + columns: headersJustTimestamp, + }, + }, + }, + }; + + const store = createStore( + state, + SUB_PLUGINS_REDUCER, + { dataTable: tGridReducer }, + kibanaObservable, + storage ); - wrapper.update(); - await waitFor(() => { - wrapper.update(); - headersJustTimestamp.forEach(() => { - expect( - wrapper - .find('[data-test-subj="data-driven-columns"]') - .first() - .find('[data-test-subj="localized-date-tool-tip"]') - .exists() - ).toEqual(true); - }); + const wrapper = await getWrapper(<StatefulBody {...props} />, { store }); + + headersJustTimestamp.forEach(() => { + expect( + wrapper + .find('[data-test-subj="data-driven-columns"]') + .first() + .find('[data-test-subj="localized-date-tool-tip"]') + .exists() + ).toEqual(true); }); - }, 20000); + }); }); describe('action on event', () => { const addaNoteToEvent = (wrapper: ReturnType<typeof mount>, note: string) => { @@ -231,14 +284,11 @@ describe.skip('Body', () => { mockDispatch.mockClear(); }); - test('Add a note to an event', () => { - const wrapper = mount( - <TestProviders> - <StatefulBody {...props} /> - </TestProviders> - ); - addaNoteToEvent(wrapper, 'hello world'); + test('Add a note to an event', async () => { + const wrapper = await getWrapper(<StatefulBody {...props} />); + addaNoteToEvent(wrapper, 'hello world'); + wrapper.update(); expect(mockDispatch).toHaveBeenNthCalledWith( 3, expect.objectContaining({ @@ -263,7 +313,7 @@ describe.skip('Body', () => { ); }); - test('Add two notes to an event', () => { + test('Add two notes to an event', async () => { const { storage } = createSecuritySolutionStorageMock(); const state: State = { ...mockGlobalState, @@ -288,13 +338,10 @@ describe.skip('Body', () => { storage ); - const Proxy = (proxyProps: Props) => ( - <TestProviders store={store}> - <StatefulBody {...proxyProps} /> - </TestProviders> - ); + const Proxy = (proxyProps: Props) => <StatefulBody {...proxyProps} />; + + const wrapper = await getWrapper(<Proxy {...props} />, { store }); - const wrapper = mount(<Proxy {...props} />); addaNoteToEvent(wrapper, 'hello world'); mockDispatch.mockClear(); addaNoteToEvent(wrapper, 'new hello world'); @@ -328,11 +375,7 @@ describe.skip('Body', () => { mockDispatch.mockReset(); }); test('call the right reduce action to show event details for query tab', async () => { - const wrapper = mount( - <TestProviders> - <StatefulBody {...props} /> - </TestProviders> - ); + const wrapper = await getWrapper(<StatefulBody {...props} />); wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click'); wrapper.update(); @@ -353,11 +396,7 @@ describe.skip('Body', () => { }); test('call the right reduce action to show event details for pinned tab', async () => { - const wrapper = mount( - <TestProviders> - <StatefulBody {...props} tabType={TimelineTabs.pinned} /> - </TestProviders> - ); + const wrapper = await getWrapper(<StatefulBody {...props} tabType={TimelineTabs.pinned} />); wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click'); wrapper.update(); @@ -378,11 +417,7 @@ describe.skip('Body', () => { }); test('call the right reduce action to show event details for notes tab', async () => { - const wrapper = mount( - <TestProviders> - <StatefulBody {...props} tabType={TimelineTabs.notes} /> - </TestProviders> - ); + const wrapper = await getWrapper(<StatefulBody {...props} tabType={TimelineTabs.notes} />); wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click'); wrapper.update(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx index 10d196596b7ba..ea06fd70bbbf2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash'; import React from 'react'; @@ -48,6 +48,11 @@ describe('get_column_renderer', () => { let auditd: Ecs; const mount = useMountAppended(); + const getWrapper = async (childrenComponent: JSX.Element) => { + const wrapper = mount(childrenComponent); + await waitFor(() => wrapper.find('[data-test-subj="suricataRefs"]').exists()); + return wrapper; + }; beforeEach(() => { nonSuricata = cloneDeep(mockTimelineData[0].ecs); suricata = cloneDeep(mockTimelineData[2].ecs); @@ -68,14 +73,15 @@ describe('get_column_renderer', () => { expect(wrapper).toMatchSnapshot(); }); - test('should render plain row data when it is a non suricata row', () => { + test('should render plain row data when it is a non suricata row', async () => { const rowRenderer = getRowRenderer({ data: nonSuricata, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ data: nonSuricata, isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + + const wrapper = await getWrapper( <TestProviders> <span>{row}</span> </TestProviders> @@ -83,14 +89,14 @@ describe('get_column_renderer', () => { expect(wrapper.text()).toEqual(''); }); - test('should render a suricata row data when it is a suricata row', () => { + test('should render a suricata row data when it is a suricata row', async () => { const rowRenderer = getRowRenderer({ data: suricata, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ data: suricata, isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <span>{row}</span> </TestProviders> @@ -100,7 +106,7 @@ describe('get_column_renderer', () => { ); }); - test('should render a suricata row data if event.category is network_traffic', () => { + test('should render a suricata row data if event.category is network_traffic', async () => { suricata.event = { ...suricata.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer({ data: suricata, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ @@ -108,7 +114,7 @@ describe('get_column_renderer', () => { isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <span>{row}</span> </TestProviders> @@ -118,7 +124,7 @@ describe('get_column_renderer', () => { ); }); - test('should render a zeek row data if event.category is network_traffic', () => { + test('should render a zeek row data if event.category is network_traffic', async () => { zeek.event = { ...zeek.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer({ data: zeek, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ @@ -126,7 +132,7 @@ describe('get_column_renderer', () => { isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <span>{row}</span> </TestProviders> @@ -136,7 +142,7 @@ describe('get_column_renderer', () => { ); }); - test('should render a system row data if event.category is network_traffic', () => { + test('should render a system row data if event.category is network_traffic', async () => { system.event = { ...system.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer({ data: system, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ @@ -144,7 +150,7 @@ describe('get_column_renderer', () => { isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <span>{row}</span> </TestProviders> @@ -154,7 +160,7 @@ describe('get_column_renderer', () => { ); }); - test('should render a auditd row data if event.category is network_traffic', () => { + test('should render a auditd row data if event.category is network_traffic', async () => { auditd.event = { ...auditd.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer({ data: auditd, rowRenderers: defaultRowRenderers }); const row = rowRenderer?.renderRow({ @@ -162,7 +168,7 @@ describe('get_column_renderer', () => { isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <span>{row}</span> </TestProviders> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx index 2a4e82a183999..38155bad81a83 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_details.test.tsx @@ -14,6 +14,7 @@ import '../../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../../common/mock/test_providers'; import { useMountAppended } from '../../../../../../common/utils/use_mount_appended'; import { SuricataDetails } from './suricata_details'; +import { waitFor } from '@testing-library/react'; jest.mock('../../../../../../common/lib/kibana'); @@ -30,14 +31,19 @@ jest.mock('../../../../../../common/components/link_to'); describe('SuricataDetails', () => { const mount = useMountAppended(); + const getWrapper = async (childrenComponent: JSX.Element) => { + const wrapper = mount(childrenComponent); + await waitFor(() => wrapper.find('[data-test-subj="suricataRefs"]').exists()); // check for presence of query input + return wrapper; + }; describe('rendering', () => { test('it renders the default SuricataDetails', () => { const wrapper = shallow(<SuricataDetails data={mockTimelineData[2].ecs} timelineId="test" />); expect(wrapper).toMatchSnapshot(); }); - test('it returns text if the data does contain suricata data', () => { - const wrapper = mount( + test('it returns text if the data does contain suricata data', async () => { + const wrapper = await getWrapper( <TestProviders> <SuricataDetails data={mockTimelineData[2].ecs} timelineId="test" /> </TestProviders> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx index 2300bcfaa5bc2..602ca3ebbd577 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_refs.tsx @@ -6,7 +6,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import { getLinksFromSignature } from './suricata_links'; @@ -18,21 +18,41 @@ const LinkEuiFlexItem = styled(EuiFlexItem)` LinkEuiFlexItem.displayName = 'LinkEuiFlexItem'; export const SuricataRefs = React.memo<{ signatureId: number }>(({ signatureId }) => { - let comp = <></>; - getLinksFromSignature(signatureId).then((links) => { - comp = ( - <EuiFlexGroup gutterSize="none" justifyContent="center" wrap> - {links.map((link) => ( + const [linksFromSignature, setLinksFromSignature] = useState<string[] | undefined>(undefined); + useEffect(() => { + let isSubscribed = true; + async function getLinks() { + if (signatureId != null) { + try { + const links = await getLinksFromSignature(signatureId); + if (isSubscribed && links != null) { + setLinksFromSignature(links); + } + } catch (exc) { + setLinksFromSignature(undefined); + } + } else if (isSubscribed) { + setLinksFromSignature(undefined); + } + } + getLinks(); + return () => { + isSubscribed = false; + }; + }, [signatureId]); + + return ( + <EuiFlexGroup data-test-subj="suricataRefs" gutterSize="none" justifyContent="center" wrap> + {linksFromSignature && + linksFromSignature.map((link) => ( <LinkEuiFlexItem key={link} grow={false}> <EuiLink href={link} color="subdued" target="_blank"> {link} </EuiLink> </LinkEuiFlexItem> ))} - </EuiFlexGroup> - ); - }); - return comp; + </EuiFlexGroup> + ); }); SuricataRefs.displayName = 'SuricataRefs'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx index 833fd2cff9666..67fb4fe5dfce8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx @@ -8,6 +8,7 @@ import { shallow } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; +import { waitFor } from '@testing-library/react'; import { removeExternalLinkText } from '@kbn/securitysolution-io-ts-utils'; import type { Ecs } from '../../../../../../../common/ecs'; @@ -31,16 +32,22 @@ jest.mock('@elastic/eui', () => { jest.mock('../../../../../../common/components/link_to'); describe('suricata_row_renderer', () => { - const mount = useMountAppended(); let nonSuricata: Ecs; let suricata: Ecs; + const mount = useMountAppended(); + + const getWrapper = async (childrenComponent: JSX.Element) => { + const wrapper = mount(childrenComponent); + await waitFor(() => wrapper.find('[data-test-subj="suricataRefs"]').exists()); + return wrapper; + }; beforeEach(() => { nonSuricata = cloneDeep(mockTimelineData[0].ecs); suricata = cloneDeep(mockTimelineData[2].ecs); }); - test('renders correctly against snapshot', () => { + test('renders correctly against snapshot', async () => { const children = suricataRowRenderer.renderRow({ data: nonSuricata, isDraggable: true, @@ -59,13 +66,14 @@ describe('suricata_row_renderer', () => { expect(suricataRowRenderer.isInstance(suricata)).toBe(true); }); - test('should render a suricata row', () => { + test('should render a suricata row', async () => { const children = suricataRowRenderer.renderRow({ data: suricata, isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + + const wrapper = await getWrapper( <TestProviders> <span>{children}</span> </TestProviders> @@ -80,14 +88,14 @@ describe('suricata_row_renderer', () => { ); }); - test('should render a suricata row even if it does not have a suricata signature', () => { + test('should render a suricata row even if it does not have a suricata signature', async () => { delete suricata?.suricata?.eve?.alert?.signature; const children = suricataRowRenderer.renderRow({ data: suricata, isDraggable: true, scopeId: TimelineId.test, }); - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <span>{children}</span> </TestProviders> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx index ddcc2df231a00..e27871d4c9018 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/index.test.tsx @@ -11,6 +11,7 @@ import { TestProviders } from '../../../../common/mock/test_providers'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { DataProviders } from '.'; +import { TimelineId } from '../../../../../common/types'; describe('DataProviders', () => { const mount = useMountAppended(); @@ -54,7 +55,7 @@ describe('DataProviders', () => { test('it may be resized vertically via a resize handle', () => { const wrapper = mount( <TestProviders> - <DataProviders timelineId="test" /> + <DataProviders timelineId={TimelineId.test} /> </TestProviders> ); @@ -67,7 +68,7 @@ describe('DataProviders', () => { test('it never grows taller than one third (33%) of the view height', () => { const wrapper = mount( <TestProviders> - <DataProviders timelineId="test" /> + <DataProviders timelineId={TimelineId.test} /> </TestProviders> ); @@ -80,7 +81,7 @@ describe('DataProviders', () => { test('it automatically displays scroll bars when the width or height of the data providers exceeds the drop target', () => { const wrapper = mount( <TestProviders> - <DataProviders timelineId="test" /> + <DataProviders timelineId={TimelineId.test} /> </TestProviders> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx index 196c2655e6ab0..eeb383f6a2298 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/index.test.tsx @@ -17,6 +17,7 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { TimelineHeader } from '.'; import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; +import { waitFor } from '@testing-library/react'; const mockUiSettingsForFilterManager = coreMock.createStart().uiSettings; @@ -25,6 +26,11 @@ jest.mock('../../../../common/lib/kibana'); describe('Header', () => { const indexPattern = mockIndexPattern; const mount = useMountAppended(); + const getWrapper = async (childrenComponent: JSX.Element) => { + const wrapper = mount(childrenComponent); + await waitFor(() => wrapper.find('[data-test-subj="timelineCallOutUnauthorized"]').exists()); + return wrapper; + }; const props = { browserFields: {}, dataProviders: mockDataProviders, @@ -48,9 +54,9 @@ describe('Header', () => { expect(wrapper).toMatchSnapshot(); }); - test('it renders the data providers when show is true', () => { + test('it renders the data providers when show is true', async () => { const testProps = { ...props, show: true }; - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <TimelineHeader {...testProps} /> </TestProviders> @@ -59,14 +65,14 @@ describe('Header', () => { expect(wrapper.find('[data-test-subj="dataProviders"]').exists()).toEqual(true); }); - test('it renders the unauthorized call out providers', () => { + test('it renders the unauthorized call out providers', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), showCallOutUnauthorizedMsg: true, }; - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <TimelineHeader {...testProps} /> </TestProviders> @@ -75,14 +81,14 @@ describe('Header', () => { expect(wrapper.find('[data-test-subj="timelineCallOutUnauthorized"]').exists()).toEqual(true); }); - test('it renders the unauthorized call out with correct icon', () => { + test('it renders the unauthorized call out with correct icon', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), showCallOutUnauthorizedMsg: true, }; - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <TimelineHeader {...testProps} /> </TestProviders> @@ -93,14 +99,14 @@ describe('Header', () => { ).toEqual('alert'); }); - test('it renders the unauthorized call out with correct message', () => { + test('it renders the unauthorized call out with correct message', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), showCallOutUnauthorizedMsg: true, }; - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <TimelineHeader {...testProps} /> </TestProviders> @@ -113,7 +119,7 @@ describe('Header', () => { ); }); - test('it renders the immutable timeline call out providers', () => { + test('it renders the immutable timeline call out providers', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), @@ -121,7 +127,7 @@ describe('Header', () => { status: TimelineStatus.immutable, }; - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <TimelineHeader {...testProps} /> </TestProviders> @@ -130,7 +136,7 @@ describe('Header', () => { expect(wrapper.find('[data-test-subj="timelineImmutableCallOut"]').exists()).toEqual(true); }); - test('it renders the immutable timeline call out with correct icon', () => { + test('it renders the immutable timeline call out with correct icon', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), @@ -138,7 +144,7 @@ describe('Header', () => { status: TimelineStatus.immutable, }; - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <TimelineHeader {...testProps} /> </TestProviders> @@ -149,7 +155,7 @@ describe('Header', () => { ).toEqual('alert'); }); - test('it renders the immutable timeline call out with correct message', () => { + test('it renders the immutable timeline call out with correct message', async () => { const testProps = { ...props, filterManager: new FilterManager(mockUiSettingsForFilterManager), @@ -157,7 +163,7 @@ describe('Header', () => { status: TimelineStatus.immutable, }; - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <TimelineHeader {...testProps} /> </TestProviders> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx index 4926b11cd9b13..ff129689fb5f9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx @@ -14,7 +14,7 @@ import { AddToFavoritesButton, NewTimeline } from './helpers'; import { useCreateTimelineButton } from './use_create_timeline'; import { kibanaObservable, TestProviders } from '../../../../common/mock/test_providers'; import { timelineActions } from '../../../store/timeline'; -import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; +import { TimelineId, TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; import { createSecuritySolutionStorageMock, mockGlobalState, @@ -100,7 +100,7 @@ describe('Favorite Button', () => { test('should render favorite button', () => { const wrapper = mount( <TestProviders> - <AddToFavoritesButton timelineId="test" /> + <AddToFavoritesButton timelineId={TimelineId.test} /> </TestProviders> ); @@ -110,7 +110,7 @@ describe('Favorite Button', () => { test('Favorite button should be enabled ', () => { const wrapper = mount( <TestProviders> - <AddToFavoritesButton timelineId="test" /> + <AddToFavoritesButton timelineId={TimelineId.test} /> </TestProviders> ); @@ -123,7 +123,7 @@ describe('Favorite Button', () => { const spy = jest.spyOn(timelineActions, 'updateIsFavorite'); const wrapper = mount( <TestProviders> - <AddToFavoritesButton timelineId="test" /> + <AddToFavoritesButton timelineId={TimelineId.test} /> </TestProviders> ); @@ -142,8 +142,8 @@ describe('Favorite Button', () => { timeline: { ...mockGlobalState.timeline, timelineById: { - test: { - ...mockGlobalState.timeline.timelineById.test, + [TimelineId.test]: { + ...mockGlobalState.timeline.timelineById[TimelineId.test], isFavorite: true, }, }, @@ -156,7 +156,7 @@ describe('Favorite Button', () => { ); const wrapper = mount( <TestProviders store={store}> - <AddToFavoritesButton timelineId="test" /> + <AddToFavoritesButton timelineId={TimelineId.test} /> </TestProviders> ); @@ -176,8 +176,8 @@ describe('Favorite Button', () => { timeline: { ...mockGlobalState.timeline, timelineById: { - test: { - ...mockGlobalState.timeline.timelineById.test, + [TimelineId.test]: { + ...mockGlobalState.timeline.timelineById[TimelineId.test], status: TimelineStatus.immutable, timelineType: TimelineType.template, templateTimelineId: 'mock-template-timeline-id', @@ -193,7 +193,7 @@ describe('Favorite Button', () => { ); const wrapper = mount( <TestProviders store={store}> - <AddToFavoritesButton timelineId="test" /> + <AddToFavoritesButton timelineId={TimelineId.test} /> </TestProviders> ); expect( @@ -212,8 +212,8 @@ describe('Favorite Button', () => { timeline: { ...mockGlobalState.timeline, timelineById: { - test: { - ...mockGlobalState.timeline.timelineById.test, + [TimelineId.test]: { + ...mockGlobalState.timeline.timelineById[TimelineId.test], status: TimelineStatus.active, timelineType: TimelineType.template, templateTimelineId: 'mock-template-timeline-id', diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index cc48a58717eee..c53360cb9168c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -28,6 +28,7 @@ import { mockSourcererScope } from '../../../../common/containers/sourcerer/mock import { Direction } from '../../../../../common/search_strategy'; import { mockCasesContext } from '@kbn/cases-plugin/public/mocks/mock_cases_context'; import * as helpers from '../../../../common/lib/kuery'; +import { waitFor } from '@testing-library/react'; jest.mock('../../../containers', () => ({ useTimelineEvents: jest.fn(), @@ -109,7 +110,11 @@ describe('Timeline', () => { const endDate = '2018-03-24T03:33:52.253Z'; const mount = useMountAppended(); - + const getWrapper = async (childrenComponent: JSX.Element) => { + const wrapper = mount(childrenComponent); + await waitFor(() => wrapper.find('[data-test-subj="timelineHeader"]').exists()); + return wrapper; + }; beforeEach(() => { (useTimelineEvents as jest.Mock).mockReturnValue([ false, @@ -162,8 +167,8 @@ describe('Timeline', () => { spyCombineQueries.mockClear(); }); - test('should trim kqlQueryExpression', () => { - mount( + test('should trim kqlQueryExpression', async () => { + await getWrapper( <TestProviders> <QueryTabContentComponent {...props} /> </TestProviders> @@ -184,8 +189,8 @@ describe('Timeline', () => { expect(wrapper.find('QueryTabContentComponent')).toMatchSnapshot(); }); - test('it renders the timeline header', () => { - const wrapper = mount( + test('it renders the timeline header', async () => { + const wrapper = await getWrapper( <TestProviders> <QueryTabContentComponent {...props} /> </TestProviders> @@ -194,8 +199,8 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="timelineHeader"]').exists()).toEqual(true); }); - test('it renders the timeline table', () => { - const wrapper = mount( + test('it renders the timeline table', async () => { + const wrapper = await getWrapper( <TestProviders> <QueryTabContentComponent {...props} /> </TestProviders> @@ -206,7 +211,7 @@ describe('Timeline', () => { ).toEqual(true); }); - test('it does render the timeline table when the source is loading with no events', () => { + test('it does render the timeline table when the source is loading with no events', async () => { (useSourcererDataView as jest.Mock).mockReturnValue({ browserFields: {}, loading: true, @@ -214,7 +219,7 @@ describe('Timeline', () => { selectedPatterns: [], missingPatterns: [], }); - const wrapper = mount( + const wrapper = await getWrapper( <TestProviders> <QueryTabContentComponent {...props} /> </TestProviders> @@ -226,8 +231,8 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); }); - test('it does NOT render the timeline table when start is empty', () => { - const wrapper = mount( + test('it does NOT render the timeline table when start is empty', async () => { + const wrapper = await getWrapper( <TestProviders> <QueryTabContentComponent {...props} start={''} /> </TestProviders> @@ -239,8 +244,8 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); }); - test('it does NOT render the timeline table when end is empty', () => { - const wrapper = mount( + test('it does NOT render the timeline table when end is empty', async () => { + const wrapper = await getWrapper( <TestProviders> <QueryTabContentComponent {...props} end={''} /> </TestProviders> @@ -252,8 +257,8 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); }); - test('it does NOT render the paging footer when you do NOT have any data providers', () => { - const wrapper = mount( + test('it does NOT render the paging footer when you do NOT have any data providers', async () => { + const wrapper = await getWrapper( <TestProviders> <QueryTabContentComponent {...props} /> </TestProviders> @@ -262,8 +267,8 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="table-pagination"]').exists()).toEqual(false); }); - it('it shows the timeline footer', () => { - const wrapper = mount( + it('it shows the timeline footer', async () => { + const wrapper = await getWrapper( <TestProviders> <QueryTabContentComponent {...props} /> </TestProviders> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.test.tsx index 773790b4da652..506353a9b7047 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.test.tsx @@ -162,6 +162,7 @@ describe('useSessionView with active timeline and a session id and graph event i height: 1000, sessionEntityId: 'test', loadAlertDetails: mockDetails, + canAccessEndpointManagement: false, }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.tsx index 8b5add5ae73b1..0cb001b6aa3e1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/session_tab_content/use_session_view.tsx @@ -34,6 +34,7 @@ import { useGlobalFullScreen, } from '../../../../common/containers/use_full_screen'; import { detectionsTimelineIds } from '../../../containers/helpers'; +import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; import { timelineDefaults } from '../../../store/timeline/defaults'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; @@ -266,6 +267,7 @@ export const useSessionView = ({ }, [scopeId]); const { globalFullScreen } = useGlobalFullScreen(); const { timelineFullScreen } = useTimelineFullScreen(); + const { canAccessEndpointManagement } = useUserPrivileges().endpointPrivileges; const defaults = isTimelineScope(scopeId) ? timelineDefaults : tableDefaults; const { sessionViewConfig, activeTab } = useDeepEqualSelector((state) => ({ @@ -310,9 +312,17 @@ export const useSessionView = ({ loadAlertDetails: openDetailsPanel, isFullScreen: fullScreen, height: heightMinusSearchBar, + canAccessEndpointManagement, }) : null; - }, [fullScreen, openDetailsPanel, sessionView, sessionViewConfig, height]); + }, [ + height, + sessionViewConfig, + sessionView, + openDetailsPanel, + fullScreen, + canAccessEndpointManagement, + ]); return { openDetailsPanel, diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts index 0d74241ed88bc..3f71bb099ed63 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts @@ -9,7 +9,7 @@ import * as api from './api'; import { KibanaServices } from '../../common/lib/kibana'; import { TimelineType, TimelineStatus } from '../../../common/types/timeline'; import { TIMELINE_DRAFT_URL, TIMELINE_URL } from '../../../common/constants'; -import type { ImportDataProps } from '../../detections/containers/detection_engine/rules/types'; +import type { ImportDataProps } from '../../detection_engine/rule_management/logic/types'; jest.mock('../../common/lib/kibana', () => { return { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.ts index cdabd37b443fe..f81910ff8013e 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.ts @@ -49,7 +49,7 @@ import type { ExportDocumentsProps, ImportDataProps, ImportDataResponse, -} from '../../detections/containers/detection_engine/rules'; +} from '../../detection_engine/rule_management/logic'; import type { TimelineInput } from '../../../common/search_strategy'; interface RequestPostTimeline { diff --git a/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx index a4b82b47b9484..e7df8a7678084 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/details/details_tabs.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React from 'react'; import { Switch } from 'react-router-dom'; import { Route } from '@kbn/kibana-react-plugin/public'; @@ -18,7 +18,6 @@ import { AnomaliesQueryTabBody } from '../../../common/containers/anomalies/anom import { usersDetailsPagePath } from '../constants'; import { TableId } from '../../../../common/types'; import { EventsQueryTabBody } from '../../../common/components/events_tab'; -import { userNameExistsFilter } from './helpers'; import { AuthenticationsQueryTabBody } from '../navigation'; export const UsersDetailsTabs = React.memo<UsersDetailsTabsProps>( @@ -32,7 +31,7 @@ export const UsersDetailsTabs = React.memo<UsersDetailsTabsProps>( to, type, detailName, - pageFilters = [], + userDetailFilter, }) => { const tabProps = { deleteQuery, @@ -46,11 +45,6 @@ export const UsersDetailsTabs = React.memo<UsersDetailsTabsProps>( userName: detailName, }; - const externalAlertPageFilters = useMemo( - () => [...userNameExistsFilter, ...pageFilters], - [pageFilters] - ); - return ( <Switch> <Route path={`${usersDetailsPagePath}/:tabName(${UsersTableType.authentications})`}> @@ -61,10 +55,9 @@ export const UsersDetailsTabs = React.memo<UsersDetailsTabsProps>( </Route> <Route path={`${usersDetailsPagePath}/:tabName(${UsersTableType.events})`}> <EventsQueryTabBody - {...tabProps} - pageFilters={pageFilters} + additionalFilters={userDetailFilter} tableId={TableId.usersPageEvents} - externalAlertPageFilters={externalAlertPageFilters} + {...tabProps} /> </Route> <Route path={`${usersDetailsPagePath}/:tabName(${UsersTableType.risk})`}> diff --git a/x-pack/plugins/security_solution/public/users/pages/details/index.tsx b/x-pack/plugins/security_solution/public/users/pages/details/index.tsx index a1491fc2e5a01..e2c6fff56eda4 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/details/index.tsx @@ -83,7 +83,7 @@ const UsersDetailsComponent: React.FC<UsersDetailsProps> = ({ ); const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); const query = useDeepEqualSelector(getGlobalQuerySelector); - const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); + const globalFilters = useDeepEqualSelector(getGlobalFiltersQuerySelector); const { signalIndexName } = useSignalIndex(); const { hasKibanaREAD, hasIndexRead } = useAlertsPrivileges(); @@ -109,14 +109,14 @@ const UsersDetailsComponent: React.FC<UsersDetailsProps> = ({ buildEsQuery( indexPattern, [query], - [...usersDetailsPageFilters, ...filters], + [...usersDetailsPageFilters, ...globalFilters], getEsQueryConfig(uiSettings) ), ]; } catch (e) { return [undefined, e]; } - }, [filters, indexPattern, query, uiSettings, usersDetailsPageFilters]); + }, [globalFilters, indexPattern, query, uiSettings, usersDetailsPageFilters]); const stringifiedAdditionalFilters = JSON.stringify(rawFilteredQuery); useInvalidFilterQuery({ @@ -251,7 +251,7 @@ const UsersDetailsComponent: React.FC<UsersDetailsProps> = ({ indexNames={selectedPatterns} indexPattern={indexPattern} isInitializing={isInitializing} - pageFilters={usersDetailsPageFilters} + userDetailFilter={usersDetailsPageFilters} setQuery={setQuery} to={to} type={type} diff --git a/x-pack/plugins/security_solution/public/users/pages/details/types.ts b/x-pack/plugins/security_solution/public/users/pages/details/types.ts index d3fc93b14b88b..139b268132578 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/types.ts +++ b/x-pack/plugins/security_solution/public/users/pages/details/types.ts @@ -45,7 +45,7 @@ export type UsersDetailsNavTab = Partial<Record<KeyUsersDetailsNavTab, NavTab>>; export type UsersDetailsTabsProps = UserBodyComponentDispatchProps & UsersQueryProps & { indexNames: string[]; - pageFilters?: Filter[]; + userDetailFilter: Filter[]; filterQuery?: string; indexPattern: DataViewBase; type: usersModel.UsersType; diff --git a/x-pack/plugins/security_solution/public/users/pages/types.ts b/x-pack/plugins/security_solution/public/users/pages/types.ts index 955b565b328a8..e3a55417451c4 100644 --- a/x-pack/plugins/security_solution/public/users/pages/types.ts +++ b/x-pack/plugins/security_solution/public/users/pages/types.ts @@ -5,14 +5,12 @@ * 2.0. */ -import type { Filter } from '@kbn/es-query'; import type { GlobalTimeArgs } from '../../common/containers/use_global_time'; import type { usersModel } from '../store'; export type UsersTabsProps = GlobalTimeArgs & { - filterQuery: string; - pageFilters?: Filter[]; + filterQuery?: string; indexNames: string[]; type: usersModel.UsersType; }; diff --git a/x-pack/plugins/security_solution/public/users/pages/users.tsx b/x-pack/plugins/security_solution/public/users/pages/users.tsx index df5b891c24e8d..7de9f603b0efe 100644 --- a/x-pack/plugins/security_solution/public/users/pages/users.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/users.tsx @@ -74,7 +74,7 @@ const UsersComponent = () => { ); const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); const query = useDeepEqualSelector(getGlobalQuerySelector); - const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); + const globalFilters = useDeepEqualSelector(getGlobalFiltersQuerySelector); const getUserRiskScoreFilterQuerySelector = useMemo( () => usersSelectors.userRiskScoreSeverityFilterSelector(), @@ -91,27 +91,27 @@ const UsersComponent = () => { const { tabName } = useParams<{ tabName: string }>(); const tabsFilters: Filter[] = React.useMemo(() => { if (tabName === UsersTableType.events) { - return filters.length > 0 ? [...filters, ...userNameExistsFilter] : userNameExistsFilter; + return [...globalFilters, ...userNameExistsFilter]; } if (tabName === UsersTableType.risk) { const severityFilter = generateSeverityFilter(severitySelection, RiskScoreEntity.user); - return [...severityFilter, ...filters]; + return [...severityFilter, ...globalFilters]; } - return filters; - }, [severitySelection, tabName, filters]); + return globalFilters; + }, [severitySelection, tabName, globalFilters]); const { indicesExist, indexPattern, selectedPatterns } = useSourcererDataView(); - const [filterQuery, kqlError] = useMemo( + const [globalFiltersQuery, kqlError] = useMemo( () => convertToBuildEsQuery({ config: getEsQueryConfig(uiSettings), indexPattern, queries: [query], - filters, + filters: globalFilters, }), - [filters, indexPattern, uiSettings, query] + [globalFilters, indexPattern, uiSettings, query] ); const [tabsFilterQuery] = useMemo( () => @@ -124,7 +124,14 @@ const UsersComponent = () => { [indexPattern, query, tabsFilters, uiSettings] ); - useInvalidFilterQuery({ id: ID, filterQuery, kqlError, query, startDate: from, endDate: to }); + useInvalidFilterQuery({ + id: ID, + filterQuery: globalFiltersQuery, + kqlError, + query, + startDate: from, + endDate: to, + }); const onSkipFocusBeforeEventsTable = useCallback(() => { containerElement.current @@ -193,12 +200,12 @@ const UsersComponent = () => { /> <UsersKpiComponent - filterQuery={filterQuery} + filterQuery={globalFiltersQuery} indexNames={selectedPatterns} from={from} setQuery={setQuery} to={to} - skip={isInitializing || !filterQuery} + skip={isInitializing || !!kqlError} updateDateRange={updateDateRange} /> @@ -210,14 +217,13 @@ const UsersComponent = () => { <UsersTabs deleteQuery={deleteQuery} - filterQuery={tabsFilterQuery || ''} + filterQuery={tabsFilterQuery} from={from} indexNames={selectedPatterns} isInitializing={isInitializing} setQuery={setQuery} to={to} type={usersModel.UsersType.page} - pageFilters={tabsFilters} /> </SecuritySolutionPageWrapper> </StyledFullHeightContainer> diff --git a/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx b/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx index a0229ccbe1c8c..510d6d1fc9c04 100644 --- a/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/users_tabs.tsx @@ -18,20 +18,11 @@ import { AnomaliesUserTable } from '../../common/components/ml/tables/anomalies_ import { UserRiskScoreQueryTabBody } from './navigation/user_risk_score_tab_body'; import { EventsQueryTabBody } from '../../common/components/events_tab'; +import { userNameExistsFilter } from './details/helpers'; import { TableId } from '../../../common/types'; export const UsersTabs = memo<UsersTabsProps>( - ({ - deleteQuery, - filterQuery, - pageFilters, - from, - indexNames, - isInitializing, - setQuery, - to, - type, - }) => { + ({ deleteQuery, filterQuery, from, indexNames, isInitializing, setQuery, to, type }) => { const tabProps = { deleteQuery, endDate: to, @@ -59,9 +50,9 @@ export const UsersTabs = memo<UsersTabsProps>( </Route> <Route path={`${USERS_PATH}/:tabName(${UsersTableType.events})`}> <EventsQueryTabBody - {...tabProps} - pageFilters={pageFilters} + additionalFilters={userNameExistsFilter} tableId={TableId.usersPageEvents} + {...tabProps} /> </Route> </Switch> diff --git a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts index 8a1aa4050d07b..e778a94e90ac9 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts @@ -8,7 +8,9 @@ import type { KbnClient } from '@kbn/test'; import type { Client } from '@elastic/elasticsearch'; import { AGENT_ACTIONS_RESULTS_INDEX } from '@kbn/fleet-plugin/common'; -import type { UploadedFile } from '../../../../common/endpoint/types/file_storage'; +import * as cborx from 'cbor-x'; +import { basename } from 'path'; +import { getFileDownloadId } from '../../../../common/endpoint/service/response_actions/get_file_download_id'; import { checkInFleetAgent } from '../../common/fleet_services'; import { sendEndpointMetadataUpdate } from '../../common/endpoint_metadata_services'; import { FleetActionGenerator } from '../../../../common/endpoint/data_generators/fleet_action_generator'; @@ -179,37 +181,73 @@ export const sendEndpointActionResponse = async ( // For `get-file`, upload a file to ES if (action.command === 'get-file' && !endpointResponse.error) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const filePath = ( + action as ActionDetails<ResponseActionGetFileOutputContent, ResponseActionGetFileParameters> + )?.parameters?.path!; + + const fileName = basename(filePath.replace(/\\/g, '/')); + // Index the file's metadata - const fileMeta = await esClient.index<UploadedFile>({ + const fileMeta = await esClient.index({ index: FILE_STORAGE_METADATA_INDEX, - id: `${action.id}.${action.hosts[0]}`, + id: getFileDownloadId(action, action.agents[0]), body: { + action_id: action.id, + agent_id: action.agents[0], + contents: [ + { + hash: { + sha256: '8d61673c9d782297b3c774ded4e3d88f31a8869a8f25cf5cdd402ba6822d1d28', + }, + name: fileName ?? 'bad_file.txt', + path: filePath, + size: 4, + type: 'file', + }, + ], file: { - created: new Date().toISOString(), - extension: 'zip', - path: '/some/path/bad_file.txt', - type: 'file', - size: 221, - name: 'bad_file.txt.zip', + attributes: ['archive', 'compressed'], + ChunkSize: 4194304, + Compression: 'deflate', + hash: { + sha256: '8d61673c9d782297b3c774ded4e3d88f31a8869a8f25cf5cdd402ba6822d1d28', + }, mime_type: 'application/zip', + name: 'upload.zip', + size: 125, Status: 'READY', - ChunkSize: 4194304, + type: 'file', }, + source: 'endpoint', }, refresh: 'wait_for', }); // Index the file content (just one chunk) - await esClient.index({ - index: FILE_STORAGE_DATA_INDEX, - id: `${fileMeta._id}.0`, - body: { - bid: fileMeta._id, - last: true, - data: 'UEsDBBQACAAIAFVeRFUAAAAAAAAAABMAAAAMACAAYmFkX2ZpbGUudHh0VVQNAAdTVjxjU1Y8Y1NWPGN1eAsAAQT1AQAABBQAAAArycgsVgCiRIWkxBSFtMycVC4AUEsHCKkCwMsTAAAAEwAAAFBLAQIUAxQACAAIAFVeRFWpAsDLEwAAABMAAAAMACAAAAAAAAAAAACkgQAAAABiYWRfZmlsZS50eHRVVA0AB1NWPGNTVjxjU1Y8Y3V4CwABBPUBAAAEFAAAAFBLBQYAAAAAAQABAFoAAABtAAAAAAA=', + // call to `.index()` copied from File plugin here: + // https://github.com/elastic/kibana/blob/main/x-pack/plugins/files/server/blob_storage_service/adapters/es/content_stream/content_stream.ts#L195 + await esClient.index( + { + index: FILE_STORAGE_DATA_INDEX, + id: `${fileMeta._id}.0`, + document: cborx.encode({ + bid: fileMeta._id, + last: true, + data: Buffer.from( + 'UEsDBAoACQAAAFZeRFWpAsDLHwAAABMAAAAMABwAYmFkX2ZpbGUudHh0VVQJAANTVjxjU1Y8Y3V4CwABBPUBAAAEFAAAAMOcoyEq/Q4VyG02U9O0LRbGlwP/y5SOCfRKqLz1rsBQSwcIqQLAyx8AAAATAAAAUEsBAh4DCgAJAAAAVl5EVakCwMsfAAAAEwAAAAwAGAAAAAAAAQAAAKSBAAAAAGJhZF9maWxlLnR4dFVUBQADU1Y8Y3V4CwABBPUBAAAEFAAAAFBLBQYAAAAAAQABAFIAAAB1AAAAAAA=', + 'base64' + ), + }), + refresh: 'wait_for', }, - refresh: 'wait_for', - }); + { + headers: { + 'content-type': 'application/cbor', + accept: 'application/json', + }, + } + ); } return endpointResponse; diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index 7639e73b0a36d..7a0d7fa1950fe 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -5,10 +5,25 @@ * 2.0. */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + import type { AwaitedProperties } from '@kbn/utility-types'; import type { ScopedClusterClientMock } from '@kbn/core/server/mocks'; -import { loggingSystemMock, savedObjectsServiceMock } from '@kbn/core/server/mocks'; -import type { SavedObjectsClientContract } from '@kbn/core/server'; +import { + elasticsearchServiceMock, + httpServerMock, + httpServiceMock, + loggingSystemMock, + savedObjectsClientMock, + savedObjectsServiceMock, +} from '@kbn/core/server/mocks'; +import type { + KibanaRequest, + RouteConfig, + SavedObjectsClientContract, + RequestHandler, + IRouter, +} from '@kbn/core/server'; import { listMock } from '@kbn/lists-plugin/server/mocks'; import { securityMock } from '@kbn/security-plugin/server/mocks'; import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; @@ -25,6 +40,8 @@ import { // a restricted path. import { createCasesClientMock } from '@kbn/cases-plugin/server/client/mocks'; import { createFleetAuthzMock } from '@kbn/fleet-plugin/common'; +import type { RequestFixtureOptions } from '@kbn/core-http-router-server-mocks'; +import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { xpackMocks } from '../fixtures'; import { createMockConfig, requestContextMock } from '../lib/detection_engine/routes/__mocks__'; import type { @@ -192,3 +209,77 @@ export function createRouteHandlerContext( context.core.savedObjects.client = savedObjectsClient; return context; } + +export interface HttpApiTestSetupMock<P = any, Q = any, B = any> { + routerMock: ReturnType<typeof httpServiceMock.createRouter>; + scopedEsClusterClientMock: ReturnType<typeof elasticsearchServiceMock.createScopedClusterClient>; + savedObjectClientMock: ReturnType<typeof savedObjectsClientMock.create>; + endpointAppContextMock: EndpointAppContext; + httpResponseMock: ReturnType<typeof httpServerMock.createResponseFactory>; + httpHandlerContextMock: ReturnType<typeof requestContextMock.convertContext>; + getEsClientMock: (type?: 'internalUser' | 'currentUser') => ElasticsearchClientMock; + createRequestMock: (options?: RequestFixtureOptions<P, Q, B>) => KibanaRequest<P, Q, B>; + /** Retrieves the handler that was registered with the `router` for a given `method` and `path` */ + getRegisteredRouteHandler: ( + method: keyof Pick<IRouter, 'get' | 'put' | 'post' | 'patch' | 'delete'>, + path: string + ) => RequestHandler; +} + +/** + * Returns all of the setup needed to test an HTTP api handler + */ +export const createHttpApiTestSetupMock = <P = any, Q = any, B = any>(): HttpApiTestSetupMock< + P, + Q, + B +> => { + const routerMock = httpServiceMock.createRouter(); + const endpointAppContextMock = createMockEndpointAppContext(); + const scopedEsClusterClientMock = elasticsearchServiceMock.createScopedClusterClient(); + const savedObjectClientMock = savedObjectsClientMock.create(); + const httpHandlerContextMock = requestContextMock.convertContext( + createRouteHandlerContext(scopedEsClusterClientMock, savedObjectClientMock) + ); + const httpResponseMock = httpServerMock.createResponseFactory(); + const getRegisteredRouteHandler: HttpApiTestSetupMock['getRegisteredRouteHandler'] = ( + method, + path + ): RequestHandler => { + const methodCalls = routerMock[method].mock.calls as Array< + [route: RouteConfig<unknown, unknown, unknown, 'get'>, handler: RequestHandler] + >; + const handler = methodCalls.find(([routeConfig]) => routeConfig.path.startsWith(path)); + + if (!handler) { + throw new Error(`Handler for [${method}][${path}] not found`); + } + + return handler[1]; + }; + + return { + routerMock, + + endpointAppContextMock, + scopedEsClusterClientMock, + savedObjectClientMock, + + httpHandlerContextMock, + httpResponseMock, + + createRequestMock: (options: RequestFixtureOptions<P, Q, B> = {}): KibanaRequest<P, Q, B> => { + return httpServerMock.createKibanaRequest<P, Q, B>(options); + }, + + getEsClientMock: ( + type: 'internalUser' | 'currentUser' = 'internalUser' + ): ElasticsearchClientMock => { + return type === 'currentUser' + ? scopedEsClusterClientMock.asCurrentUser + : scopedEsClusterClientMock.asInternalUser; + }, + + getRegisteredRouteHandler, + }; +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_download_handler.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_download_handler.test.ts new file mode 100644 index 0000000000000..5711baac65f54 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_download_handler.test.ts @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + getActionFileDownloadRouteHandler, + registerActionFileDownloadRoutes, +} from './file_download_handler'; +import type { HttpApiTestSetupMock } from '../../mocks'; +import { createHttpApiTestSetupMock } from '../../mocks'; +import type { EndpointActionFileDownloadParams } from '../../../../common/endpoint/schema/actions'; +import { getActionDetailsById as _getActionDetailsById } from '../../services'; +import { EndpointAuthorizationError, NotFoundError } from '../../errors'; +import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator'; +import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; +import { getFileDownloadStream as _getFileDownloadStream } from '../../services/actions/action_files'; +import stream from 'stream'; +import type { ActionDetails } from '../../../../common/endpoint/types'; +import { ACTION_AGENT_FILE_DOWNLOAD_ROUTE } from '../../../../common/endpoint/constants'; + +jest.mock('../../services'); +jest.mock('../../services/actions/action_files'); + +describe('Response Actions file download API', () => { + const getActionDetailsById = _getActionDetailsById as jest.Mock; + const getFileDownloadStream = _getFileDownloadStream as jest.Mock; + + let apiTestSetup: HttpApiTestSetupMock; + let httpRequestMock: ReturnType< + HttpApiTestSetupMock<EndpointActionFileDownloadParams>['createRequestMock'] + >; + let httpHandlerContextMock: HttpApiTestSetupMock<EndpointActionFileDownloadParams>['httpHandlerContextMock']; + let httpResponseMock: HttpApiTestSetupMock<EndpointActionFileDownloadParams>['httpResponseMock']; + + beforeEach(() => { + apiTestSetup = createHttpApiTestSetupMock<EndpointActionFileDownloadParams>(); + + ({ httpHandlerContextMock, httpResponseMock } = apiTestSetup); + httpRequestMock = apiTestSetup.createRequestMock({ + params: { action_id: '111', agent_id: '222' }, + }); + }); + + describe('#registerActionFileDownloadRoutes()', () => { + beforeEach(() => { + registerActionFileDownloadRoutes( + apiTestSetup.routerMock, + apiTestSetup.endpointAppContextMock + ); + }); + + it('should register the route', () => { + expect( + apiTestSetup.getRegisteredRouteHandler('get', ACTION_AGENT_FILE_DOWNLOAD_ROUTE) + ).toBeDefined(); + }); + + it('should error if user has no authz to api', async () => { + const authz = (await httpHandlerContextMock.securitySolution).endpointAuthz; + authz.canWriteFileOperations = false; + + await apiTestSetup.getRegisteredRouteHandler('get', ACTION_AGENT_FILE_DOWNLOAD_ROUTE)( + httpHandlerContextMock, + httpRequestMock, + httpResponseMock + ); + + expect(httpResponseMock.forbidden).toHaveBeenCalledWith({ + body: expect.any(EndpointAuthorizationError), + }); + }); + }); + + describe('Route handler', () => { + let fileDownloadHandler: ReturnType<typeof getActionFileDownloadRouteHandler>; + let esClientMock: ReturnType<HttpApiTestSetupMock['getEsClientMock']>; + let action: ActionDetails; + + beforeEach(() => { + esClientMock = apiTestSetup.getEsClientMock(); + action = new EndpointActionGenerator().generateActionDetails({ + id: '111', + agents: ['222'], + }); + fileDownloadHandler = getActionFileDownloadRouteHandler(apiTestSetup.endpointAppContextMock); + + getActionDetailsById.mockImplementation(async () => { + return action; + }); + + getFileDownloadStream.mockImplementation(async () => { + return { + stream: new stream.Readable(), + fileName: 'test.txt', + mimeType: 'text/plain', + }; + }); + }); + + it('should error if action ID is invalid', async () => { + getActionDetailsById.mockImplementationOnce(async () => { + throw new NotFoundError('not found'); + }); + await fileDownloadHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); + + expect(httpResponseMock.notFound).toHaveBeenCalled(); + }); + + it('should error if agent id is not in the action', async () => { + action.agents = ['333']; + await fileDownloadHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); + + expect(httpResponseMock.customError).toHaveBeenCalledWith({ + statusCode: 400, + body: expect.any(CustomHttpRequestError), + }); + }); + + it('should retrieve the download Stream using correct file ID', async () => { + await fileDownloadHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); + + expect(getFileDownloadStream).toHaveBeenCalledWith( + esClientMock, + expect.anything(), + '111.222' + ); + }); + + it('should respond with expected HTTP headers', async () => { + await fileDownloadHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); + + expect(httpResponseMock.ok).toHaveBeenCalledWith( + expect.objectContaining({ + headers: { + 'cache-control': 'max-age=31536000, immutable', + 'content-disposition': 'attachment; filename="test.txt"', + 'content-type': 'application/octet-stream', + 'x-content-type-options': 'nosniff', + }, + }) + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_download_handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_download_handler.ts new file mode 100644 index 0000000000000..304c92e6d0184 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_download_handler.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { RequestHandler } from '@kbn/core/server'; +import { getFileDownloadId } from '../../../../common/endpoint/service/response_actions/get_file_download_id'; +import { getActionDetailsById } from '../../services'; +import { errorHandler } from '../error_handler'; +import { ACTION_AGENT_FILE_DOWNLOAD_ROUTE } from '../../../../common/endpoint/constants'; +import type { EndpointActionFileDownloadParams } from '../../../../common/endpoint/schema/actions'; +import { EndpointActionFileDownloadSchema } from '../../../../common/endpoint/schema/actions'; +import { withEndpointAuthz } from '../with_endpoint_authz'; +import type { EndpointAppContext } from '../../types'; +import type { + SecuritySolutionPluginRouter, + SecuritySolutionRequestHandlerContext, +} from '../../../types'; +import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; +import { getFileDownloadStream } from '../../services/actions/action_files'; + +export const registerActionFileDownloadRoutes = ( + router: SecuritySolutionPluginRouter, + endpointContext: EndpointAppContext +) => { + const logger = endpointContext.logFactory.get('actionFileDownload'); + + router.get( + { + path: ACTION_AGENT_FILE_DOWNLOAD_ROUTE, + validate: EndpointActionFileDownloadSchema, + options: { authRequired: true, tags: ['access:securitySolution'] }, + }, + withEndpointAuthz( + { all: ['canWriteFileOperations'] }, + logger, + getActionFileDownloadRouteHandler(endpointContext) + ) + ); +}; + +export const getActionFileDownloadRouteHandler = ( + endpointContext: EndpointAppContext +): RequestHandler< + EndpointActionFileDownloadParams, + unknown, + unknown, + SecuritySolutionRequestHandlerContext +> => { + const logger = endpointContext.logFactory.get('actionFileDownload'); + + return async (context, req, res) => { + const { action_id: actionId, agent_id: agentId } = req.params; + const esClient = (await context.core).elasticsearch.client.asInternalUser; + const endpointMetadataService = endpointContext.service.getEndpointMetadataService(); + + try { + // Ensure action id is valid and that it was sent to the Agent ID requested. + const actionDetails = await getActionDetailsById(esClient, endpointMetadataService, actionId); + + if (!actionDetails.agents.includes(agentId)) { + throw new CustomHttpRequestError(`Action was not sent to agent id [${agentId}]`, 400); + } + + const fileDownloadId = getFileDownloadId(actionDetails, agentId); + const { stream, fileName } = await getFileDownloadStream(esClient, logger, fileDownloadId); + + return res.ok({ + body: stream, + headers: { + 'content-type': 'application/octet-stream', + 'cache-control': 'max-age=31536000, immutable', + // Note, this name can be overridden by the client if set via a "download" attribute on the HTML tag. + 'content-disposition': `attachment; filename="${fileName ?? 'download.zip'}"`, + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + 'x-content-type-options': 'nosniff', + }, + }); + } catch (error) { + return errorHandler(logger, res, error); + } + }; +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_info_handler.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_info_handler.test.ts new file mode 100644 index 0000000000000..fa3c7e392d91e --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_info_handler.test.ts @@ -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 { getActionDetailsById as _getActionDetailsById } from '../../services'; +import type { HttpApiTestSetupMock } from '../../mocks'; +import { createHttpApiTestSetupMock } from '../../mocks'; +import type { EndpointActionFileDownloadParams } from '../../../../common/endpoint/schema/actions'; +import { getActionFileInfoRouteHandler, registerActionFileInfoRoute } from './file_info_handler'; +import { ACTION_AGENT_FILE_INFO_ROUTE } from '../../../../common/endpoint/constants'; +import { EndpointAuthorizationError, NotFoundError } from '../../errors'; +import type { ActionDetails } from '../../../../common/endpoint/types'; +import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator'; +import { getFileInfo as _getFileInfo } from '../../services/actions/action_files'; +import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; + +jest.mock('../../services'); +jest.mock('../../services/actions/action_files'); + +describe('Response Action file info API', () => { + const getActionDetailsById = _getActionDetailsById as jest.Mock; + const getFileInfo = _getFileInfo as jest.Mock; + + let apiTestSetup: HttpApiTestSetupMock; + let httpRequestMock: ReturnType< + HttpApiTestSetupMock<EndpointActionFileDownloadParams>['createRequestMock'] + >; + let httpHandlerContextMock: HttpApiTestSetupMock<EndpointActionFileDownloadParams>['httpHandlerContextMock']; + let httpResponseMock: HttpApiTestSetupMock<EndpointActionFileDownloadParams>['httpResponseMock']; + + beforeEach(() => { + apiTestSetup = createHttpApiTestSetupMock<EndpointActionFileDownloadParams>(); + + ({ httpHandlerContextMock, httpResponseMock } = apiTestSetup); + httpRequestMock = apiTestSetup.createRequestMock({ + params: { action_id: '111', agent_id: '222' }, + }); + }); + + describe('#registerActionFileInfoRoute()', () => { + beforeEach(() => { + registerActionFileInfoRoute(apiTestSetup.routerMock, apiTestSetup.endpointAppContextMock); + }); + + it('should register the route', () => { + expect( + apiTestSetup.getRegisteredRouteHandler('get', ACTION_AGENT_FILE_INFO_ROUTE) + ).toBeDefined(); + }); + + it('should error if user has no authz to api', async () => { + const authz = (await httpHandlerContextMock.securitySolution).endpointAuthz; + authz.canWriteFileOperations = false; + + await apiTestSetup.getRegisteredRouteHandler('get', ACTION_AGENT_FILE_INFO_ROUTE)( + httpHandlerContextMock, + httpRequestMock, + httpResponseMock + ); + + expect(httpResponseMock.forbidden).toHaveBeenCalledWith({ + body: expect.any(EndpointAuthorizationError), + }); + }); + }); + + describe('Route handler', () => { + let fileInfoHandler: ReturnType<typeof getActionFileInfoRouteHandler>; + let esClientMock: ReturnType<HttpApiTestSetupMock['getEsClientMock']>; + let action: ActionDetails; + + beforeEach(() => { + esClientMock = apiTestSetup.getEsClientMock(); + action = new EndpointActionGenerator().generateActionDetails({ + id: '111', + agents: ['222'], + }); + fileInfoHandler = getActionFileInfoRouteHandler(apiTestSetup.endpointAppContextMock); + + getActionDetailsById.mockImplementation(async () => { + return action; + }); + + getFileInfo.mockImplementation(async () => { + return { + created: '2022-10-10T14:57:30.682Z', + id: '123', + mimeType: 'text/plain', + name: 'test.txt', + size: 1234, + status: 'READY', + }; + }); + }); + + it('should error if action ID is invalid', async () => { + getActionDetailsById.mockImplementationOnce(async () => { + throw new NotFoundError('not found'); + }); + await fileInfoHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); + + expect(httpResponseMock.notFound).toHaveBeenCalled(); + }); + + it('should error if agent id is not in the action', async () => { + action.agents = ['333']; + await fileInfoHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); + + expect(httpResponseMock.customError).toHaveBeenCalledWith({ + statusCode: 400, + body: expect.any(CustomHttpRequestError), + }); + }); + + it('should retrieve the file info with correct file id', async () => { + await fileInfoHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); + + expect(getFileInfo).toHaveBeenCalledWith(esClientMock, expect.anything(), '111.222'); + }); + + it('should respond with expected output', async () => { + await fileInfoHandler(httpHandlerContextMock, httpRequestMock, httpResponseMock); + + expect(httpResponseMock.ok).toHaveBeenCalledWith({ + body: { + data: { + created: '2022-10-10T14:57:30.682Z', + id: '123', + mimeType: 'text/plain', + name: 'test.txt', + size: 1234, + status: 'READY', + }, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_info_handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_info_handler.ts new file mode 100644 index 0000000000000..e8290d1c3d3b1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/file_info_handler.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 type { RequestHandler } from '@kbn/core/server'; +import { getFileInfo } from '../../services/actions/action_files'; +import { getActionDetailsById } from '../../services'; +import { ACTION_AGENT_FILE_INFO_ROUTE } from '../../../../common/endpoint/constants'; +import type { EndpointAppContext } from '../../types'; +import type { EndpointActionFileInfoParams } from '../../../../common/endpoint/schema/actions'; +import type { + SecuritySolutionRequestHandlerContext, + SecuritySolutionPluginRouter, +} from '../../../types'; +import { EndpointActionFileInfoSchema } from '../../../../common/endpoint/schema/actions'; +import { withEndpointAuthz } from '../with_endpoint_authz'; +import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; +import { getFileDownloadId } from '../../../../common/endpoint/service/response_actions/get_file_download_id'; +import { errorHandler } from '../error_handler'; + +export const getActionFileInfoRouteHandler = ( + endpointContext: EndpointAppContext +): RequestHandler< + EndpointActionFileInfoParams, + unknown, + unknown, + SecuritySolutionRequestHandlerContext +> => { + const logger = endpointContext.logFactory.get('actionFileInfo'); + + return async (context, req, res) => { + const { action_id: actionId, agent_id: agentId } = req.params; + const esClient = (await context.core).elasticsearch.client.asInternalUser; + const endpointMetadataService = endpointContext.service.getEndpointMetadataService(); + + try { + // Ensure action id is valid and that it was sent to the Agent ID requested. + const actionDetails = await getActionDetailsById(esClient, endpointMetadataService, actionId); + + if (!actionDetails.agents.includes(agentId)) { + throw new CustomHttpRequestError(`Action was not sent to agent id [${agentId}]`, 400); + } + + const fileId = getFileDownloadId(actionDetails, agentId); + + return res.ok({ + body: { + data: await getFileInfo(esClient, logger, fileId), + }, + }); + } catch (error) { + return errorHandler(logger, res, error); + } + }; +}; + +export const registerActionFileInfoRoute = ( + router: SecuritySolutionPluginRouter, + endpointContext: EndpointAppContext +) => { + router.get( + { + path: ACTION_AGENT_FILE_INFO_ROUTE, + validate: EndpointActionFileInfoSchema, + options: { authRequired: true, tags: ['access:securitySolution'] }, + }, + withEndpointAuthz( + { all: ['canWriteFileOperations'] }, + endpointContext.logFactory.get('actionFileInfo'), + getActionFileInfoRouteHandler(endpointContext) + ) + ); +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/index.ts index d947cfefa9a2a..e5b3e90de8878 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/index.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { registerActionFileInfoRoute } from './file_info_handler'; +import { registerActionFileDownloadRoutes } from './file_download_handler'; import { registerActionDetailsRoutes } from './details'; import type { SecuritySolutionPluginRouter } from '../../../types'; import type { EndpointAppContext } from '../../types'; @@ -23,5 +25,7 @@ export function registerActionRoutes( registerActionAuditLogRoutes(router, endpointContext); registerActionListRoutes(router, endpointContext); registerActionDetailsRoutes(router, endpointContext); + registerActionFileDownloadRoutes(router, endpointContext); registerResponseActionRoutes(router, endpointContext); + registerActionFileInfoRoute(router, endpointContext); } diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_files.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_files.test.ts new file mode 100644 index 0000000000000..ffbe39f0435a5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_files.test.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClientMock } from '@kbn/core/server/mocks'; +import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import type { Logger } from '@kbn/core/server'; +import { createEsFileClient as _createEsFileClient } from '@kbn/files-plugin/server'; +import { createFileClientMock } from '@kbn/files-plugin/server/mocks'; +import { getFileDownloadStream, getFileInfo } from './action_files'; +import type { DiagnosticResult } from '@elastic/elasticsearch'; +import { errors } from '@elastic/elasticsearch'; +import { NotFoundError } from '../../errors'; +import { FILE_STORAGE_DATA_INDEX } from '../../../../common/endpoint/constants'; +import { BaseDataGenerator } from '../../../../common/endpoint/data_generators/base_data_generator'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +jest.mock('@kbn/files-plugin/server'); +const createEsFileClient = _createEsFileClient as jest.Mock; + +describe('Action Files service', () => { + let loggerMock: Logger; + let esClientMock: ElasticsearchClientMock; + let fileClientMock: ReturnType<typeof createFileClientMock>; + + beforeEach(() => { + loggerMock = loggingSystemMock.create().get('mock'); + esClientMock = elasticsearchServiceMock.createElasticsearchClient(); + fileClientMock = createFileClientMock(); + createEsFileClient.mockReturnValue(fileClientMock); + }); + + describe('#getFileDownloadStream()', () => { + it('should return expected output', async () => { + await expect(getFileDownloadStream(esClientMock, loggerMock, '123')).resolves.toEqual({ + stream: expect.anything(), + fileName: 'test.txt', + mimeType: 'text/plain', + }); + }); + + it('should return NotFoundError if file or index is not found', async () => { + fileClientMock.get.mockRejectedValue( + new errors.ResponseError({ + statusCode: 404, + } as DiagnosticResult) + ); + + await expect(getFileDownloadStream(esClientMock, loggerMock, '123')).rejects.toBeInstanceOf( + NotFoundError + ); + }); + }); + + describe('#getFileInfo()', () => { + let fileChunksEsResponseMock: estypes.SearchResponse; + + beforeEach(() => { + fileChunksEsResponseMock = BaseDataGenerator.toEsSearchResponse([ + BaseDataGenerator.toEsSearchHit({}), + ]); + + esClientMock.search.mockImplementation(async (searchRequest) => { + if (searchRequest && searchRequest.index === FILE_STORAGE_DATA_INDEX) { + return fileChunksEsResponseMock; + } + + return BaseDataGenerator.toEsSearchResponse([]); + }); + }); + + it('should return expected output', async () => { + await expect(getFileInfo(esClientMock, loggerMock, '123')).resolves.toEqual({ + created: '2022-10-10T14:57:30.682Z', + id: '123', + mimeType: 'text/plain', + name: 'test.txt', + size: 1234, + status: 'READY', + }); + }); + + it('should check if file has chunks if status is `READY`', async () => { + fileChunksEsResponseMock = BaseDataGenerator.toEsSearchResponse([]); + + await expect(getFileInfo(esClientMock, loggerMock, '123')).resolves.toEqual({ + created: '2022-10-10T14:57:30.682Z', + id: '123', + mimeType: 'text/plain', + name: 'test.txt', + size: 1234, + status: 'DELETED', + }); + + expect(loggerMock.debug).toHaveBeenCalledWith( + 'File with id [123] has no data chunks. Status will be adjusted to DELETED' + ); + }); + + it('should return a `NotFoundError` if file id is not found', async () => { + fileClientMock.get.mockRejectedValue( + new errors.ResponseError({ + statusCode: 404, + } as DiagnosticResult) + ); + + await expect(getFileInfo(esClientMock, loggerMock, '123')).rejects.toBeInstanceOf( + NotFoundError + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_files.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_files.ts new file mode 100644 index 0000000000000..cb161ac189d59 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_files.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 type { ElasticsearchClient, Logger } from '@kbn/core/server'; +import type { Readable } from 'stream'; +import type { FileClient } from '@kbn/files-plugin/server'; +import { createEsFileClient } from '@kbn/files-plugin/server'; +import { errors } from '@elastic/elasticsearch'; +import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { UploadedFileInfo } from '../../../../common/endpoint/types'; +import { NotFoundError } from '../../errors'; +import { + FILE_STORAGE_DATA_INDEX, + FILE_STORAGE_METADATA_INDEX, +} from '../../../../common/endpoint/constants'; +import { EndpointError } from '../../../../common/endpoint/errors'; + +const getFileClient = (esClient: ElasticsearchClient, logger: Logger): FileClient => { + return createEsFileClient({ + metadataIndex: FILE_STORAGE_METADATA_INDEX, + blobStorageIndex: FILE_STORAGE_DATA_INDEX, + elasticsearchClient: esClient, + logger, + }); +}; + +const getFileRetrievalError = ( + error: Error | errors.ResponseError, + fileId: string +): EndpointError => { + if (error instanceof errors.ResponseError) { + const statusCode = error.statusCode; + + // 404 will be returned if file id is not found -or- index does not exist yet. + // Using the `NotFoundError` error class will result in the API returning a 404 + if (statusCode === 404) { + return new NotFoundError(`File with id [${fileId}] not found`, error); + } + } + + return new EndpointError(`Failed to get file using id [${fileId}]: ${error.message}`, error); +}; + +/** + * Returns a NodeJS `Readable` data stream to a file + * @param esClient + * @param logger + * @param fileId + */ +export const getFileDownloadStream = async ( + esClient: ElasticsearchClient, + logger: Logger, + fileId: string +): Promise<{ stream: Readable; fileName: string; mimeType?: string }> => { + try { + const fileClient = getFileClient(esClient, logger); + const file = await fileClient.get({ id: fileId }); + const { name: fileName, mimeType } = file.data; + + return { + stream: await file.downloadContent(), + fileName, + mimeType, + }; + } catch (error) { + throw getFileRetrievalError(error, fileId); + } +}; + +/** + * Retrieve information about a file + * + * @param esClient + * @param logger + * @param fileId + */ +export const getFileInfo = async ( + esClient: ElasticsearchClient, + logger: Logger, + fileId: string +): Promise<UploadedFileInfo> => { + try { + const fileClient = getFileClient(esClient, logger); + const file = await fileClient.get({ id: fileId }); + const { name, id, mimeType, size, status, created } = file.data; + let fileHasChunks: boolean = true; + + if (status === 'READY') { + fileHasChunks = await doesFileHaveChunks(esClient, fileId); + + if (!fileHasChunks) { + logger.debug( + `File with id [${fileId}] has no data chunks. Status will be adjusted to DELETED` + ); + } + } + + // TODO: add `ttl` to the return payload by retrieving the value from ILM? + + return { + name, + id, + mimeType, + size, + created, + status: fileHasChunks ? status : 'DELETED', + }; + } catch (error) { + throw getFileRetrievalError(error, fileId); + } +}; + +const doesFileHaveChunks = async ( + esClient: ElasticsearchClient, + fileId: string +): Promise<boolean> => { + const chunks = await esClient.search({ + index: FILE_STORAGE_DATA_INDEX, + body: { + query: { + term: { + 'bid.keyword': fileId, + }, + }, + // Setting `_source` to false - we don't need the actual document to be returned + _source: false, + }, + }); + + return Boolean((chunks.hits?.total as SearchTotalHits)?.value); +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts index 377c9bc3caf74..920540fd30082 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts @@ -444,7 +444,7 @@ describe('When using Actions service utilities', () => { outputs: { '456': { content: { - code: 'ra_get-file_success', + code: 'ra_get-file_success_done', path: '/some/path/bad_file.txt', size: 1234, zip_size: 123, diff --git a/x-pack/plugins/security_solution/server/features.ts b/x-pack/plugins/security_solution/server/features.ts index fc3307a650097..64a082eaea9a6 100644 --- a/x-pack/plugins/security_solution/server/features.ts +++ b/x-pack/plugins/security_solution/server/features.ts @@ -185,6 +185,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( subFeatures: experimentalFeatures.endpointRbacEnabled ? [ { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.endpointList.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Endpoint List access.', + } + ), name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.endpointList', { defaultMessage: 'Endpoint List', }), @@ -195,7 +202,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeEndpointList`, `${APP_ID}-readEndpointList`], id: 'endpoint_list_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -206,7 +213,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readEndpointList`], id: 'endpoint_list_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -219,6 +226,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.trustedApplications.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Trusted Applications access.', + } + ), name: i18n.translate( 'xpack.securitySolution.featureRegistry.subFeatures.trustedApplications', { @@ -232,7 +246,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeTrustedApplications`, `${APP_ID}-readTrustedApplications`], id: 'trusted_applications_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -243,7 +257,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readTrustedApplications`], id: 'trusted_applications_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -256,6 +270,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Host Isolation Exceptions access.', + } + ), name: i18n.translate( 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions', { @@ -272,7 +293,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( `${APP_ID}-readHostIsolationExceptions`, ], id: 'host_isolation_exceptions_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -283,7 +304,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readHostIsolationExceptions`], id: 'host_isolation_exceptions_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -296,6 +317,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.blockList.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Blocklist access.', + } + ), name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.blockList', { defaultMessage: 'Blocklist', }), @@ -306,7 +334,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeBlocklist`, `${APP_ID}-readBlocklist`], id: 'blocklist_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -317,7 +345,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readBlocklist`], id: 'blocklist_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -330,6 +358,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.eventFilters.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Event Filters access.', + } + ), name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.eventFilters', { defaultMessage: 'Event Filters', }), @@ -340,7 +375,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeEventFilters`, `${APP_ID}-readEventFilters`], id: 'event_filters_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -351,7 +386,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readEventFilters`], id: 'event_filters_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -364,6 +399,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.policyManagement.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Policy Management access.', + } + ), name: i18n.translate( 'xpack.securitySolution.featureRegistry.subFeatures.policyManagement', { @@ -377,7 +419,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writePolicyManagement`, `${APP_ID}-readPolicyManagement`], id: 'policy_management_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -388,7 +430,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readPolicyManagement`], id: 'policy_management_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -401,6 +443,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.actionsLogManagement.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Actions Log Management access.', + } + ), name: i18n.translate( 'xpack.securitySolution.featureRegistry.subFeatures.actionsLogManagement', { @@ -417,7 +466,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( `${APP_ID}-readActionsLogManagement`, ], id: 'actions_log_management_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -428,7 +477,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-readActionsLogManagement`], id: 'actions_log_management_read', - includeIn: 'read', + includeIn: 'none', name: 'Read', savedObject: { all: [], @@ -441,6 +490,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolation.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Host Isolation access.', + } + ), name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.hostIsolation', { defaultMessage: 'Host Isolation', }), @@ -451,7 +507,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeHostIsolation`], id: 'host_isolation_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -464,6 +520,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.processOperations.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Process Operations access.', + } + ), name: i18n.translate( 'xpack.securitySolution.featureRegistry.subFeatures.processOperations', { @@ -477,7 +540,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeProcessOperations`], id: 'process_operations_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], @@ -490,6 +553,13 @@ export const getKibanaPrivilegesFeaturePrivileges = ( ], }, { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.fileOperations.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for File Operations access.', + } + ), name: i18n.translate('xpack.securitySolution.featureRegistr.subFeatures.fileOperations', { defaultMessage: 'File Operations', }), @@ -500,7 +570,7 @@ export const getKibanaPrivilegesFeaturePrivileges = ( { api: [`${APP_ID}-writeFileOperations`], id: 'file_operations_all', - includeIn: 'all', + includeIn: 'none', name: 'All', savedObject: { all: [], diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts index da52432571f97..d4a60be85d3ea 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/install_prepackaged_rules.ts @@ -9,7 +9,7 @@ import type { KibanaRequest, Logger } from '@kbn/core/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { PluginStartContract as AlertsStartContract } from '@kbn/alerting-plugin/server'; import { createDetectionIndex } from '../../lib/detection_engine/routes/index/create_index_route'; -import { createPrepackagedRules } from '../../lib/detection_engine/routes/rules/add_prepackaged_rules_route'; +import { createPrepackagedRules } from '../../lib/detection_engine/prebuilt_rules'; import type { SecuritySolutionApiRequestHandlerContext } from '../../types'; export interface InstallPrepackagedRulesProps { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/fleet/get_installed_integrations/installed_integration_set.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/installed_integration_set.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/fleet/get_installed_integrations/installed_integration_set.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/installed_integration_set.ts index e124e0322192c..156d1c99fde5d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/fleet/get_installed_integrations/installed_integration_set.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/installed_integration_set.ts @@ -14,7 +14,7 @@ import type { InstalledPackage, InstalledPackageArray, InstalledPackageBasicInfo, -} from '../../../../../../common/detection_engine/schemas/common'; +} from '../../../../../../common/detection_engine/fleet_integrations'; export interface IInstalledIntegrationSet { addPackagePolicy(policy: PackagePolicy): void; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/fleet/get_installed_integrations/get_installed_integrations_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts similarity index 91% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/fleet/get_installed_integrations/get_installed_integrations_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts index 9e9091e1b578c..7b904c282e1e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/fleet/get_installed_integrations/get_installed_integrations_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts @@ -8,11 +8,11 @@ import type { Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { initPromisePool } from '../../../../../utils/promise_pool'; -import { buildSiemResponse } from '../../utils'; +import { buildSiemResponse } from '../../../routes/utils'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import { DETECTION_ENGINE_INSTALLED_INTEGRATIONS_URL } from '../../../../../../common/constants'; -import type { GetInstalledIntegrationsResponse } from '../../../../../../common/detection_engine/schemas/response/get_installed_integrations_response_schema'; +import type { GetInstalledIntegrationsResponse } from '../../../../../../common/detection_engine/fleet_integrations'; +import { GET_INSTALLED_INTEGRATIONS_URL } from '../../../../../../common/detection_engine/fleet_integrations'; import { createInstalledIntegrationSet } from './installed_integration_set'; const MAX_CONCURRENT_REQUESTS_TO_PACKAGE_REGISTRY = 5; @@ -26,7 +26,7 @@ export const getInstalledIntegrationsRoute = ( ) => { router.get( { - path: DETECTION_ENGINE_INSTALLED_INTEGRATIONS_URL, + path: GET_INSTALLED_INTEGRATIONS_URL, validate: {}, options: { tags: ['access:securitySolution'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/register_routes.ts new file mode 100644 index 0000000000000..2c6c2c2ae21f8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/register_routes.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. + */ + +import type { Logger } from '@kbn/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; + +import { getInstalledIntegrationsRoute } from './get_installed_integrations/route'; + +export const registerFleetIntegrationsRoutes = ( + router: SecuritySolutionPluginRouter, + logger: Logger +) => { + getInstalledIntegrationsRoute(router, logger); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/index.ts new file mode 100644 index 0000000000000..c0123e587d9bf --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/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 * from './api/register_routes'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/route.test.ts similarity index 89% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/route.test.ts index 5853aecf13021..fd7351c677a2a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/route.test.ts @@ -5,24 +5,24 @@ * 2.0. */ -import { getPrepackagedRulesStatusRoute } from './get_prepackaged_rules_status_route'; +import { getPrebuiltRulesAndTimelinesStatusRoute } from './route'; import { getEmptyFindResult, getFindResultWithSingleHit, getPrepackagedRulesStatusRequest, -} from '../__mocks__/request_responses'; -import { requestContextMock, serverMock, createMockConfig } from '../__mocks__'; +} from '../../../routes/__mocks__/request_responses'; +import { requestContextMock, serverMock, createMockConfig } from '../../../routes/__mocks__'; import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; -import { checkTimelinesStatus } from '../../../timeline/utils/check_timelines_status'; +import { checkTimelinesStatus } from '../../../../timeline/utils/check_timelines_status'; import { mockCheckTimelinesStatusBeforeInstallResult, mockCheckTimelinesStatusAfterInstallResult, -} from '../../../timeline/__mocks__/import_timelines'; +} from '../../../../timeline/__mocks__/import_timelines'; -jest.mock('../../rules/get_prepackaged_rules', () => { +jest.mock('../../logic/get_latest_prebuilt_rules', () => { return { - getLatestPrepackagedRules: async () => { + getLatestPrebuiltRules: async () => { return [ { rule_id: 'rule-1', @@ -43,8 +43,8 @@ jest.mock('../../rules/get_prepackaged_rules', () => { }; }); -jest.mock('../../../timeline/utils/check_timelines_status', () => { - const actual = jest.requireActual('../../../timeline/utils/check_timelines_status'); +jest.mock('../../../../timeline/utils/check_timelines_status', () => { + const actual = jest.requireActual('../../../../timeline/utils/check_timelines_status'); return { ...actual, checkTimelinesStatus: jest.fn(), @@ -82,7 +82,7 @@ describe('get_prepackaged_rule_status_route', () => { prepackagedTimelines: [], }); - getPrepackagedRulesStatusRoute(server.router, createMockConfig(), securitySetup); + getPrebuiltRulesAndTimelinesStatusRoute(server.router, createMockConfig(), securitySetup); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/route.ts new file mode 100644 index 0000000000000..e8f947b0540c1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/route.ts @@ -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 { transformError } from '@kbn/securitysolution-es-utils'; +import { validate } from '@kbn/securitysolution-io-ts-utils'; +import { buildSiemResponse } from '../../../routes/utils'; +import type { ConfigType } from '../../../../../config'; +import type { SetupPlugins } from '../../../../../plugin'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; + +import { + PREBUILT_RULES_STATUS_URL, + GetPrebuiltRulesAndTimelinesStatusResponse, +} from '../../../../../../common/detection_engine/prebuilt_rules'; + +import { getExistingPrepackagedRules } from '../../../rule_management/logic/search/get_existing_prepackaged_rules'; +import { findRules } from '../../../rule_management/logic/search/find_rules'; +import { getLatestPrebuiltRules } from '../../logic/get_latest_prebuilt_rules'; +import { getRulesToInstall } from '../../logic/get_rules_to_install'; +import { getRulesToUpdate } from '../../logic/get_rules_to_update'; +import { ruleAssetSavedObjectsClientFactory } from '../../logic/rule_asset/rule_asset_saved_objects_client'; +import { rulesToMap } from '../../logic/utils'; + +import { buildFrameworkRequest } from '../../../../timeline/utils/common'; +import { + checkTimelinesStatus, + checkTimelineStatusRt, +} from '../../../../timeline/utils/check_timelines_status'; + +export const getPrebuiltRulesAndTimelinesStatusRoute = ( + router: SecuritySolutionPluginRouter, + config: ConfigType, + security: SetupPlugins['security'] +) => { + router.get( + { + path: PREBUILT_RULES_STATUS_URL, + validate: false, + options: { + tags: ['access:securitySolution'], + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + const ctx = await context.resolve(['core', 'alerting']); + const savedObjectsClient = ctx.core.savedObjects.client; + const rulesClient = ctx.alerting.getRulesClient(); + const ruleAssetsClient = ruleAssetSavedObjectsClientFactory(savedObjectsClient); + + try { + const latestPrebuiltRules = await getLatestPrebuiltRules( + ruleAssetsClient, + config.prebuiltRulesFromFileSystem, + config.prebuiltRulesFromSavedObjects + ); + + const customRules = await findRules({ + rulesClient, + perPage: 1, + page: 1, + sortField: 'enabled', + sortOrder: 'desc', + filter: 'alert.attributes.params.immutable: false', + fields: undefined, + }); + + const installedPrebuiltRules = rulesToMap( + await getExistingPrepackagedRules({ rulesClient }) + ); + + const rulesToInstall = getRulesToInstall(latestPrebuiltRules, installedPrebuiltRules); + const rulesToUpdate = getRulesToUpdate(latestPrebuiltRules, installedPrebuiltRules); + + const frameworkRequest = await buildFrameworkRequest(context, security, request); + const prebuiltTimelineStatus = await checkTimelinesStatus(frameworkRequest); + const [validatedPrebuiltTimelineStatus] = validate( + prebuiltTimelineStatus, + checkTimelineStatusRt + ); + + const responseBody: GetPrebuiltRulesAndTimelinesStatusResponse = { + rules_custom_installed: customRules.total, + rules_installed: installedPrebuiltRules.size, + rules_not_installed: rulesToInstall.length, + rules_not_updated: rulesToUpdate.length, + timelines_installed: validatedPrebuiltTimelineStatus?.prepackagedTimelines.length ?? 0, + timelines_not_installed: validatedPrebuiltTimelineStatus?.timelinesToInstall.length ?? 0, + timelines_not_updated: validatedPrebuiltTimelineStatus?.timelinesToUpdate.length ?? 0, + }; + + const [validatedBody, validationError] = validate( + responseBody, + GetPrebuiltRulesAndTimelinesStatusResponse + ); + + if (validationError != null) { + return siemResponse.error({ statusCode: 500, body: validationError }); + } else { + return response.ok({ body: validatedBody ?? {} }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/route.test.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/route.test.ts index 6c6cfde2cbaa8..f529369438742 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/route.test.ts @@ -11,28 +11,30 @@ import { getFindResultWithSingleHit, getRuleMock, getBasicEmptySearchResponse, -} from '../__mocks__/request_responses'; -import { requestContextMock, serverMock } from '../__mocks__'; -import type { AddPrepackagedRulesSchema } from '../../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; -import { addPrepackedRulesRoute, createPrepackagedRules } from './add_prepackaged_rules_route'; +} from '../../../routes/__mocks__/request_responses'; +import { requestContextMock, serverMock } from '../../../routes/__mocks__'; +import type { PrebuiltRuleToInstall } from '../../../../../../common/detection_engine/prebuilt_rules'; +import { installPrebuiltRulesAndTimelinesRoute, createPrepackagedRules } from './route'; import { listMock } from '@kbn/lists-plugin/server/mocks'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; -import { installPrepackagedTimelines } from '../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; +import { installPrepackagedTimelines } from '../../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; -import { legacyMigrate } from '../../rules/utils'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; +import { legacyMigrate } from '../../../rule_management'; -jest.mock('../../rules/utils', () => { - const actual = jest.requireActual('../../rules/utils'); +jest.mock('../../../rule_management/logic/rule_actions/legacy_action_migration', () => { + const actual = jest.requireActual( + '../../../rule_management/logic/rule_actions/legacy_action_migration' + ); return { ...actual, legacyMigrate: jest.fn(), }; }); -jest.mock('../../rules/get_prepackaged_rules', () => { +jest.mock('../../logic/get_latest_prebuilt_rules', () => { return { - getLatestPrepackagedRules: async (): Promise<AddPrepackagedRulesSchema[]> => { + getLatestPrebuiltRules: async (): Promise<PrebuiltRuleToInstall[]> => { return [ { author: ['Elastic'], @@ -66,7 +68,7 @@ jest.mock('../../rules/get_prepackaged_rules', () => { }; }); -jest.mock('../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines', () => { +jest.mock('../../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines', () => { return { installPrepackagedTimelines: jest.fn().mockResolvedValue({ success: true, @@ -105,7 +107,7 @@ describe('add_prepackaged_rules_route', () => { context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); - addPrepackedRulesRoute(server.router); + installPrebuiltRulesAndTimelinesRoute(server.router); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/route.ts similarity index 71% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/route.ts index 9960101f399c7..4399627692e22 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/route.ts @@ -13,30 +13,30 @@ import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { SecuritySolutionApiRequestHandlerContext, SecuritySolutionPluginRouter, -} from '../../../../types'; - -import type { PrePackagedRulesAndTimelinesSchema } from '../../../../../common/detection_engine/schemas/response/prepackaged_rules_schema'; -import { prePackagedRulesAndTimelinesSchema } from '../../../../../common/detection_engine/schemas/response/prepackaged_rules_schema'; -import { importTimelineResultSchema } from '../../../../../common/types/timeline'; -import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../../common/constants'; - -import { getLatestPrepackagedRules } from '../../rules/get_prepackaged_rules'; -import { installPrepackagedRules } from '../../rules/install_prepacked_rules'; -import { updatePrepackagedRules } from '../../rules/update_prepacked_rules'; -import { getRulesToInstall } from '../../rules/get_rules_to_install'; -import { getRulesToUpdate } from '../../rules/get_rules_to_update'; -import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules'; -import { ruleAssetSavedObjectsClientFactory } from '../../rules/rule_asset/rule_asset_saved_objects_client'; - -import { buildSiemResponse } from '../utils'; - -import { installPrepackagedTimelines } from '../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; -import { rulesToMap } from '../../rules/utils'; - -export const addPrepackedRulesRoute = (router: SecuritySolutionPluginRouter) => { +} from '../../../../../types'; + +import { + PREBUILT_RULES_URL, + InstallPrebuiltRulesAndTimelinesResponse, +} from '../../../../../../common/detection_engine/prebuilt_rules'; +import { importTimelineResultSchema } from '../../../../../../common/types/timeline'; + +import { getExistingPrepackagedRules } from '../../../rule_management/logic/search/get_existing_prepackaged_rules'; +import { getLatestPrebuiltRules } from '../../logic/get_latest_prebuilt_rules'; +import { createPrebuiltRules } from '../../logic/create_prebuilt_rules'; +import { updatePrebuiltRules } from '../../logic/update_prebuilt_rules'; +import { getRulesToInstall } from '../../logic/get_rules_to_install'; +import { getRulesToUpdate } from '../../logic/get_rules_to_update'; +import { ruleAssetSavedObjectsClientFactory } from '../../logic/rule_asset/rule_asset_saved_objects_client'; +import { rulesToMap } from '../../logic/utils'; + +import { installPrepackagedTimelines } from '../../../../timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; +import { buildSiemResponse } from '../../../routes/utils'; + +export const installPrebuiltRulesAndTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.put( { - path: DETECTION_ENGINE_PREPACKAGED_URL, + path: PREBUILT_RULES_URL, validate: false, options: { tags: ['access:securitySolution'], @@ -84,7 +84,7 @@ export const createPrepackagedRules = async ( context: SecuritySolutionApiRequestHandlerContext, rulesClient: RulesClient, exceptionsClient?: ExceptionListClient -): Promise<PrePackagedRulesAndTimelinesSchema | null> => { +): Promise<InstallPrebuiltRulesAndTimelinesResponse | null> => { const config = context.getConfig(); const frameworkRequest = context.getFrameworkRequest(); const savedObjectsClient = context.core.savedObjects.client; @@ -107,7 +107,7 @@ export const createPrepackagedRules = async ( await exceptionsListClient.createEndpointList(); } - const latestPrepackagedRulesMap = await getLatestPrepackagedRules( + const latestPrepackagedRulesMap = await getLatestPrebuiltRules( ruleAssetsClient, prebuiltRulesFromFileSystem, prebuiltRulesFromSavedObjects @@ -116,7 +116,8 @@ export const createPrepackagedRules = async ( const rulesToInstall = getRulesToInstall(latestPrepackagedRulesMap, installedPrePackagedRules); const rulesToUpdate = getRulesToUpdate(latestPrepackagedRulesMap, installedPrePackagedRules); - await installPrepackagedRules(rulesClient, rulesToInstall); + await createPrebuiltRules(rulesClient, rulesToInstall); + const timeline = await installPrepackagedTimelines( maxTimelineImportExportSize, frameworkRequest, @@ -126,28 +127,32 @@ export const createPrepackagedRules = async ( timeline, importTimelineResultSchema ); - await updatePrepackagedRules( + + await updatePrebuiltRules( rulesClient, savedObjectsClient, rulesToUpdate, context.getRuleExecutionLog() ); - const prepackagedRulesOutput: PrePackagedRulesAndTimelinesSchema = { + const prepackagedRulesOutput: InstallPrebuiltRulesAndTimelinesResponse = { rules_installed: rulesToInstall.length, rules_updated: rulesToUpdate.length, timelines_installed: prepackagedTimelinesResult?.timelines_installed ?? 0, timelines_updated: prepackagedTimelinesResult?.timelines_updated ?? 0, }; + const [validated, genericErrors] = validate( prepackagedRulesOutput, - prePackagedRulesAndTimelinesSchema + InstallPrebuiltRulesAndTimelinesResponse ); + if (genericErrors != null && timelinesErrors != null) { throw new PrepackagedRulesError( [genericErrors, timelinesErrors].filter((msg) => msg != null).join(', '), 500 ); } + return validated; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/register_routes.ts new file mode 100644 index 0000000000000..39e822af3e147 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/register_routes.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 { ConfigType } from '../../../../config'; +import type { SetupPlugins } from '../../../../plugin_contract'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; + +import { getPrebuiltRulesAndTimelinesStatusRoute } from './get_prebuilt_rules_and_timelines_status/route'; +import { installPrebuiltRulesAndTimelinesRoute } from './install_prebuilt_rules_and_timelines/route'; + +export const registerPrebuiltRulesRoutes = ( + router: SecuritySolutionPluginRouter, + config: ConfigType, + security: SetupPlugins['security'] +) => { + getPrebuiltRulesAndTimelinesStatusRoute(router, config, security); + installPrebuiltRulesAndTimelinesRoute(router); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_403_response_to_a_post.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/apm_403_response_to_a_post.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_403_response_to_a_post.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/apm_403_response_to_a_post.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_405_response_method_not_allowed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/apm_405_response_method_not_allowed.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_405_response_method_not_allowed.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/apm_405_response_method_not_allowed.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_sqlmap_user_agent.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/apm_sqlmap_user_agent.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/apm_sqlmap_user_agent.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/apm_sqlmap_user_agent.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_cloudtrail_logging_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_cloudtrail_logging_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_cloudtrail_logging_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_cloudtrail_logging_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_email_powershell_exchange_mailbox.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_email_powershell_exchange_mailbox.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_email_powershell_exchange_mailbox.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_gcp_pub_sub_subscription_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_gcp_pub_sub_topic_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_google_drive_ownership_transferred_via_google_workspace.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_google_drive_ownership_transferred_via_google_workspace.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_google_drive_ownership_transferred_via_google_workspace.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_google_drive_ownership_transferred_via_google_workspace.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_google_workspace_custom_gmail_route_created_or_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_google_workspace_custom_gmail_route_created_or_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_google_workspace_custom_gmail_route_created_or_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_google_workspace_custom_gmail_route_created_or_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_microsoft_365_new_inbox_rule.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_posh_audio_capture.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_audio_capture.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_posh_audio_capture.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_posh_keylogger.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_keylogger.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_posh_keylogger.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_posh_screen_grabber.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_posh_screen_grabber.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_posh_screen_grabber.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_update_event_hub_auth_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_update_event_hub_auth_rule.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_update_event_hub_auth_rule.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_update_event_hub_auth_rule.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_winrar_encryption.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/collection_winrar_encryption.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/collection_winrar_encryption.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_certutil_network_connection.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_certutil_network_connection.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_certutil_network_connection.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_beacon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_cobalt_strike_beacon.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_beacon.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_cobalt_strike_beacon.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_cobalt_strike_default_teamserver_cert.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_common_webservices.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_common_webservices.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_common_webservices.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_connection_attempt_by_non_ssh_root_session.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_connection_attempt_by_non_ssh_root_session.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_connection_attempt_by_non_ssh_root_session.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_connection_attempt_by_non_ssh_root_session.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_dns_tunneling_nslookup.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_download_rar_powershell_from_internet.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_encrypted_channel_freesslcert.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_fin7_c2_behavior.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_fin7_c2_behavior.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_fin7_c2_behavior.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_fin7_c2_behavior.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_halfbaked_beacon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_halfbaked_beacon.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_halfbaked_beacon.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_halfbaked_beacon.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_iexplore_via_com.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_iexplore_via_com.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_iexplore_via_com.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_linux_iodine_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_linux_iodine_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_linux_iodine_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_linux_iodine_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ml_packetbeat_dns_tunneling.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_ml_packetbeat_dns_tunneling.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ml_packetbeat_dns_tunneling.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_ml_packetbeat_dns_tunneling.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ml_packetbeat_rare_dns_question.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_ml_packetbeat_rare_dns_question.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ml_packetbeat_rare_dns_question.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_ml_packetbeat_rare_dns_question.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ml_packetbeat_rare_urls.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_ml_packetbeat_rare_urls.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ml_packetbeat_rare_urls.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_ml_packetbeat_rare_urls.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ml_packetbeat_rare_user_agent.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_ml_packetbeat_rare_user_agent.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_ml_packetbeat_rare_user_agent.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_ml_packetbeat_rare_user_agent.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_nat_traversal_port_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_nat_traversal_port_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_nat_traversal_port_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_port_26_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_26_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_port_26_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_port_forwarding_added_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_port_forwarding_added_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_port_forwarding_added_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_rdp_remote_desktop_protocol_from_the_internet.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_rdp_tunnel_plink.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_rdp_tunnel_plink.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_rdp_tunnel_plink.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_remote_file_copy_desktopimgdownldr.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_remote_file_copy_mpcmdrun.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_remote_file_copy_powershell.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_powershell.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_remote_file_copy_powershell.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_remote_file_copy_scripts.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_remote_file_copy_scripts.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_remote_file_copy_scripts.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_sunburst_c2_activity_detected.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_teamviewer_remote_file_copy.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_telnet_port_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_telnet_port_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_telnet_port_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_tunneling_via_earthworm.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_tunneling_via_earthworm.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_tunneling_via_earthworm.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_tunneling_via_earthworm.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_vnc_virtual_network_computing_from_the_internet.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/command_and_control_vnc_virtual_network_computing_to_the_internet.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_access_to_browser_credentials_procargs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_access_to_browser_credentials_procargs.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_access_to_browser_credentials_procargs.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_access_to_browser_credentials_procargs.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempted_bypass_of_okta_mfa.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_attempted_bypass_of_okta_mfa.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempted_bypass_of_okta_mfa.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_attempted_bypass_of_okta_mfa.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempts_to_brute_force_okta_user_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_attempts_to_brute_force_okta_user_account.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_attempts_to_brute_force_okta_user_account.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_attempts_to_brute_force_okta_user_account.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_aws_iam_assume_role_brute_force.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_azure_full_network_packet_capture_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_azure_full_network_packet_capture_detected.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_azure_full_network_packet_capture_detected.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_azure_full_network_packet_capture_detected.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_admin_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_bruteforce_admin_account.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_admin_account.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_bruteforce_admin_account.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_multiple_logon_failure_followed_by_success.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_bruteforce_multiple_logon_failure_followed_by_success.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_multiple_logon_failure_followed_by_success.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_bruteforce_multiple_logon_failure_followed_by_success.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_multiple_logon_failure_same_srcip.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_bruteforce_multiple_logon_failure_same_srcip.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_multiple_logon_failure_same_srcip.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_bruteforce_multiple_logon_failure_same_srcip.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_passowrd_guessing.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_bruteforce_passowrd_guessing.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_bruteforce_passowrd_guessing.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_bruteforce_passowrd_guessing.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_cmdline_dump_tool.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cmdline_dump_tool.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_cmdline_dump_tool.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_collection_sensitive_files.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_collection_sensitive_files.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_collection_sensitive_files.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cookies_chromium_browsers_debugging.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_cookies_chromium_browsers_debugging.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_cookies_chromium_browsers_debugging.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_cookies_chromium_browsers_debugging.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_copy_ntds_sam_volshadowcp_cmdline.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_credential_dumping_msbuild.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credential_dumping_msbuild.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_credential_dumping_msbuild.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credentials_keychains.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_credentials_keychains.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_credentials_keychains.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_credentials_keychains.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_dcsync_replication_rights.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dcsync_replication_rights.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_dcsync_replication_rights.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_disable_kerberos_preauth.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_disable_kerberos_preauth.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_disable_kerberos_preauth.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_domain_backup_dpapi_private_keys.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_dump_registry_hives.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dump_registry_hives.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_dump_registry_hives.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_dumping_hashes_bi_cmds.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_keychain_security.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_dumping_keychain_security.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_dumping_keychain_security.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_dumping_keychain_security.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_endgame_cred_dumping_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_endgame_cred_dumping_detected.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_endgame_cred_dumping_detected.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_endgame_cred_dumping_detected.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_endgame_cred_dumping_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_endgame_cred_dumping_prevented.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_endgame_cred_dumping_prevented.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_endgame_cred_dumping_prevented.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_generic_localdumps.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_generic_localdumps.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_generic_localdumps.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_generic_localdumps.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iam_user_addition_to_group.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_iam_user_addition_to_group.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iam_user_addition_to_group.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_iam_user_addition_to_group.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_iis_apppoolsa_pwd_appcmd.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_iis_connectionstrings_dumping.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_kerberoasting_unusual_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberoasting_unusual_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_kerberoasting_unusual_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberosdump_kcc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_kerberosdump_kcc.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_kerberosdump_kcc.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_kerberosdump_kcc.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_key_vault_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_key_vault_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_key_vault_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_key_vault_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_keychain_pwd_retrieval_security_cmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_keychain_pwd_retrieval_security_cmd.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_keychain_pwd_retrieval_security_cmd.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_keychain_pwd_retrieval_security_cmd.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_handle_via_malseclogon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_lsass_handle_via_malseclogon.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_handle_via_malseclogon.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_lsass_handle_via_malseclogon.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_lsass_memdump_file_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_file_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_lsass_memdump_file_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_handle_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_lsass_memdump_handle_access.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_lsass_memdump_handle_access.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_lsass_memdump_handle_access.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mfa_push_brute_force.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_mfa_push_brute_force.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mfa_push_brute_force.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_mfa_push_brute_force.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_microsoft_365_brute_force_user_account_attempt.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_microsoft_365_potential_password_spraying_attack.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_mimikatz_memssp_default_logs.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_powershell_module.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_mimikatz_powershell_module.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mimikatz_powershell_module.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_mimikatz_powershell_module.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_mitm_localhost_webproxy.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mitm_localhost_webproxy.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_mitm_localhost_webproxy.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_auth_spike_in_failed_logon_events.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_auth_spike_in_failed_logon_events.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_auth_spike_in_failed_logon_events.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_auth_spike_in_failed_logon_events.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_auth_spike_in_logon_events.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_auth_spike_in_logon_events.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_auth_spike_in_logon_events.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_auth_spike_in_logon_events.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_auth_spike_in_logon_events_from_a_source_ip.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_auth_spike_in_logon_events_from_a_source_ip.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_auth_spike_in_logon_events_from_a_source_ip.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_auth_spike_in_logon_events_from_a_source_ip.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_linux_anomalous_metadata_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_linux_anomalous_metadata_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_linux_anomalous_metadata_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_linux_anomalous_metadata_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_linux_anomalous_metadata_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_linux_anomalous_metadata_user.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_linux_anomalous_metadata_user.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_linux_anomalous_metadata_user.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_suspicious_login_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_suspicious_login_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_suspicious_login_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_suspicious_login_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_windows_anomalous_metadata_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_windows_anomalous_metadata_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_windows_anomalous_metadata_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_windows_anomalous_metadata_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_windows_anomalous_metadata_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_windows_anomalous_metadata_user.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ml_windows_anomalous_metadata_user.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ml_windows_anomalous_metadata_user.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_mod_wdigest_security_provider.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_mod_wdigest_security_provider.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_mod_wdigest_security_provider.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_moving_registry_hive_via_smb.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_moving_registry_hive_via_smb.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_moving_registry_hive_via_smb.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_moving_registry_hive_via_smb.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_okta_brute_force_or_password_spraying.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_okta_brute_force_or_password_spraying.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_okta_brute_force_or_password_spraying.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_okta_brute_force_or_password_spraying.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_persistence_network_logon_provider_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_persistence_network_logon_provider_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_persistence_network_logon_provider_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_persistence_network_logon_provider_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_posh_minidump.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_minidump.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_posh_minidump.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_posh_request_ticket.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_posh_request_ticket.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_posh_request_ticket.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce_root.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce_root.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce_root.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_potential_linux_ssh_bruteforce_root.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_lsa_memdump_via_mirrordump.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_potential_lsa_memdump_via_mirrordump.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_lsa_memdump_via_mirrordump.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_potential_lsa_memdump_via_mirrordump.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_macos_ssh_bruteforce.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_potential_macos_ssh_bruteforce.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_potential_macos_ssh_bruteforce.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_potential_macos_ssh_bruteforce.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_promt_for_pwd_via_osascript.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_relay_ntlm_auth_via_http_spoolss.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_relay_ntlm_auth_via_http_spoolss.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_relay_ntlm_auth_via_http_spoolss.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_relay_ntlm_auth_via_http_spoolss.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_remote_sam_secretsdump.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_remote_sam_secretsdump.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_remote_sam_secretsdump.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_remote_sam_secretsdump.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_root_console_failure_brute_force.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_root_console_failure_brute_force.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_root_console_failure_brute_force.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_root_console_failure_brute_force.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vault_winlog.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_saved_creds_vault_winlog.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vault_winlog.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_saved_creds_vault_winlog.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_saved_creds_vaultcmd.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_saved_creds_vaultcmd.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_saved_creds_vaultcmd.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_secretsmanager_getsecretvalue.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_seenabledelegationprivilege_assigned_to_user.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_shadow_credentials.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_shadow_credentials.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_shadow_credentials.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_shadow_credentials.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_spn_attribute_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_spn_attribute_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_spn_attribute_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_spn_attribute_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ssh_backdoor_log.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ssh_backdoor_log.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_ssh_backdoor_log.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_ssh_backdoor_log.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_storage_account_key_regenerated.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_storage_account_key_regenerated.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_storage_account_key_regenerated.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_storage_account_key_regenerated.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_comsvcs_imageload.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_suspicious_comsvcs_imageload.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_comsvcs_imageload.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_suspicious_comsvcs_imageload.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_memdump.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_suspicious_lsass_access_memdump.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_memdump.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_suspicious_lsass_access_memdump.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_via_snapshot.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_suspicious_lsass_access_via_snapshot.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_lsass_access_via_snapshot.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_suspicious_lsass_access_via_snapshot.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_winreg_access_via_sebackup_priv.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_suspicious_winreg_access_via_sebackup_priv.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_suspicious_winreg_access_via_sebackup_priv.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_suspicious_winreg_access_via_sebackup_priv.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_symbolic_link_to_shadow_copy_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_systemkey_dumping.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_systemkey_dumping.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_systemkey_dumping.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_user_excessive_sso_logon_errors.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_user_excessive_sso_logon_errors.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_user_excessive_sso_logon_errors.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_user_excessive_sso_logon_errors.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_user_impersonation_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_user_impersonation_access.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_user_impersonation_access.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_user_impersonation_access.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_via_snapshot_lsass_clone_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_via_snapshot_lsass_clone_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/credential_access_via_snapshot_lsass_clone_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/credential_access_via_snapshot_lsass_clone_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_mismatched_id.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_agent_spoofing_mismatched_id.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_mismatched_id.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_agent_spoofing_mismatched_id.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_multiple_hosts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_agent_spoofing_multiple_hosts.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_agent_spoofing_multiple_hosts.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_agent_spoofing_multiple_hosts.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_amsienable_key_mod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_amsienable_key_mod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_apple_softupdates_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_apple_softupdates_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_apple_softupdates_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_application_removed_from_blocklist_in_google_workspace.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_application_removed_from_blocklist_in_google_workspace.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_application_removed_from_blocklist_in_google_workspace.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_application_removed_from_blocklist_in_google_workspace.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_attempt_del_quarantine_attrib.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_deactivate_okta_network_zone.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_attempt_to_deactivate_okta_network_zone.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_deactivate_okta_network_zone.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_attempt_to_deactivate_okta_network_zone.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_delete_okta_network_zone.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_attempt_to_delete_okta_network_zone.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_delete_okta_network_zone.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_attempt_to_delete_okta_network_zone.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_attempt_to_disable_gatekeeper.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_syslog_service.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_attempt_to_disable_syslog_service.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_attempt_to_disable_syslog_service.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_attempt_to_disable_syslog_service.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_application_credential_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_azure_application_credential_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_application_credential_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_azure_application_credential_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_automation_runbook_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_azure_automation_runbook_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_automation_runbook_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_azure_automation_runbook_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_blob_permissions_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_azure_blob_permissions_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_blob_permissions_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_azure_blob_permissions_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_diagnostic_settings_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_azure_diagnostic_settings_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_diagnostic_settings_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_azure_diagnostic_settings_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_service_principal_addition.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_azure_service_principal_addition.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_azure_service_principal_addition.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_azure_service_principal_addition.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_base16_or_base32_encoding_or_decoding_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_base16_or_base32_encoding_or_decoding_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_base16_or_base32_encoding_or_decoding_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_base16_or_base32_encoding_or_decoding_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_chattr_immutable_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_chattr_immutable_file.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_chattr_immutable_file.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_chattr_immutable_file.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_console_history.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_clearing_windows_console_history.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_console_history.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_clearing_windows_console_history.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_clearing_windows_event_logs.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_clearing_windows_security_logs.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_cloudtrail_logging_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_cloudtrail_logging_suspended.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_cloudwatch_alarm_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_config_service_rule_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_config_service_rule_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_config_service_rule_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_config_service_rule_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_configuration_recorder_stopped.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_configuration_recorder_stopped.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_configuration_recorder_stopped.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_configuration_recorder_stopped.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_create_mod_root_certificate.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_create_mod_root_certificate.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_create_mod_root_certificate.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_cve_2020_0601.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_cve_2020_0601.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_cve_2020_0601.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_defender_disabled_via_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_defender_exclusion_via_powershell.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_delete_volume_usn_journal_with_fsutil.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_deleting_websvr_access_logs.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_deletion_of_bash_command_line_history.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_disable_posh_scriptblocklogging.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_selinux_attempt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_disable_selinux_attempt.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_selinux_attempt.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_disable_selinux_attempt.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_disable_windows_firewall_rules_with_netsh.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_disabling_windows_defender_powershell.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_logs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_disabling_windows_logs.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_disabling_windows_logs.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_disabling_windows_logs.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dns_over_https_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_dns_over_https_enabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dns_over_https_enabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_dns_over_https_enabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_domain_added_to_google_workspace_trusted_domains.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_domain_added_to_google_workspace_trusted_domains.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_domain_added_to_google_workspace_trusted_domains.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_domain_added_to_google_workspace_trusted_domains.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_dotnet_compiler_parent_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_ec2_flow_log_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_ec2_network_acl_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elastic_agent_service_terminated.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_elastic_agent_service_terminated.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elastic_agent_service_terminated.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_elastic_agent_service_terminated.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_elasticache_security_group_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_elasticache_security_group_modified_or_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_enable_inbound_rdp_with_netsh.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_enable_network_discovery_with_netsh.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_event_hub_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_event_hub_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_event_hub_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_event_hub_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_control_panel_suspicious_args.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_control_panel_suspicious_args.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_control_panel_suspicious_args.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_control_panel_suspicious_args.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_lolbas_wuauclt.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_msbuild_started_by_office_app.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_msbuild_started_by_script.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_msbuild_started_by_system_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_msbuild_started_renamed.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_msbuild_started_unusal_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_suspicious_explorer_winword.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_windefend_unusual_path.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_windefend_unusual_path.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_execution_windefend_unusual_path.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_execution_windefend_unusual_path.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_file_creation_mult_extension.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_creation_mult_extension.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_file_creation_mult_extension.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_deletion_via_shred.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_file_deletion_via_shred.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_deletion_via_shred.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_file_deletion_via_shred.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_mod_writable_dir.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_file_mod_writable_dir.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_file_mod_writable_dir.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_file_mod_writable_dir.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_firewall_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_firewall_policy_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_firewall_policy_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_firewall_policy_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_from_unusual_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_from_unusual_directory.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_from_unusual_directory.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_from_unusual_directory.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_frontdoor_firewall_policy_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_firewall_rule_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_firewall_rule_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_firewall_rule_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_firewall_rule_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_firewall_rule_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_firewall_rule_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_firewall_rule_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_logging_bucket_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_logging_sink_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_pub_sub_subscription_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_pub_sub_topic_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_configuration_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_storage_bucket_configuration_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_configuration_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_storage_bucket_configuration_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_permissions_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_storage_bucket_permissions_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_storage_bucket_permissions_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_storage_bucket_permissions_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_network_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_network_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_network_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_network_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_route_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_route_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_route_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_route_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_route_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_route_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_route_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_gcp_virtual_private_cloud_route_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_google_workspace_bitlocker_setting_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_google_workspace_bitlocker_setting_disabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_google_workspace_bitlocker_setting_disabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_google_workspace_bitlocker_setting_disabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_google_workspace_restrictions_for_google_marketplace_changed_to_allow_any_app.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_google_workspace_restrictions_for_google_marketplace_changed_to_allow_any_app.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_google_workspace_restrictions_for_google_marketplace_changed_to_allow_any_app.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_google_workspace_restrictions_for_google_marketplace_changed_to_allow_any_app.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_guardduty_detector_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hidden_file_dir_tmp.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_hidden_file_dir_tmp.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hidden_file_dir_tmp.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_hidden_file_dir_tmp.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hidden_shared_object.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_hidden_shared_object.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hidden_shared_object.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_hidden_shared_object.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_hide_encoded_executable_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_iis_httplogging_disabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_injection_msbuild.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_injection_msbuild.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_injection_msbuild.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_install_root_certificate.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_install_root_certificate.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_install_root_certificate.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_installutil_beacon.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_installutil_beacon.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_installutil_beacon.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kernel_module_removal.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_kernel_module_removal.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kernel_module_removal.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_kernel_module_removal.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kubernetes_events_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_kubernetes_events_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_kubernetes_events_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_kubernetes_events_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_log_files_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_log_files_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_log_files_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_log_files_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_masquerading_as_elastic_endpoint_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_masquerading_renamed_autoit.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_masquerading_suspicious_werfault_childproc.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_masquerading_trusted_directory.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_masquerading_werfault.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_masquerading_werfault.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_masquerading_werfault.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_365_exchange_dlp_policy_removed.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_mailboxauditbypassassociation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_365_mailboxauditbypassassociation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_365_mailboxauditbypassassociation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_365_mailboxauditbypassassociation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_microsoft_defender_tampering.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_misc_lolbin_connecting_to_the_internet.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_modify_environment_launchctl.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_modify_environment_launchctl.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_modify_environment_launchctl.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_ms_office_suspicious_regmod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_msbuild_making_network_connections.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_mshta_beacon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_mshta_beacon.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_mshta_beacon.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_mshta_beacon.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_msxsl_network.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_msxsl_network.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_msxsl_network.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_network_connection_from_windows_binary.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_watcher_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_network_watcher_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_network_watcher_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_network_watcher_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_deactivate_okta_policy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_deactivate_okta_policy.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_deactivate_okta_policy.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_deactivate_okta_policy.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_deactivate_okta_policy_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_deactivate_okta_policy_rule.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_deactivate_okta_policy_rule.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_deactivate_okta_policy_rule.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_delete_okta_policy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_delete_okta_policy.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_delete_okta_policy.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_delete_okta_policy.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_delete_okta_policy_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_delete_okta_policy_rule.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_delete_okta_policy_rule.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_delete_okta_policy_rule.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_network_zone.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_network_zone.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_network_zone.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_network_zone.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_policy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_policy.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_policy.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_policy.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_policy_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_policy_rule.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_policy_rule.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_okta_attempt_to_modify_okta_policy_rule.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_parent_process_pid_spoofing.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_parent_process_pid_spoofing.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_parent_process_pid_spoofing.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_parent_process_pid_spoofing.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_persistence_temp_scheduled_task.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_persistence_temp_scheduled_task.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_persistence_temp_scheduled_task.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_persistence_temp_scheduled_task.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_posh_assembly_load.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_assembly_load.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_posh_assembly_load.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_posh_compressed.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_compressed.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_posh_compressed.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_posh_process_injection.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_posh_process_injection.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_posh_process_injection.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_potential_processherpaderping.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_potential_processherpaderping.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_potential_processherpaderping.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_potential_processherpaderping.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_powershell_windows_firewall_disabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privacy_controls_tcc_database_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_privacy_controls_tcc_database_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privacy_controls_tcc_database_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_privacy_controls_tcc_database_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_process_termination_followed_by_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_process_termination_followed_by_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_process_termination_followed_by_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_process_termination_followed_by_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_proxy_execution_via_msdt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_proxy_execution_via_msdt.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_proxy_execution_via_msdt.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_proxy_execution_via_msdt.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_rundll32_no_arguments.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_rundll32_no_arguments.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_rundll32_no_arguments.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_rundll32_no_arguments.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_s3_bucket_configuration_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_safari_config_change.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_safari_config_change.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_safari_config_change.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_sandboxed_office_app_suspicious_zip_file.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_scheduledjobs_at_protocol_enabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_sdelete_like_filename_rename.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_sip_provider_mod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_sip_provider_mod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_sip_provider_mod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suppression_rule_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suppression_rule_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suppression_rule_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suppression_rule_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_certutil_commands.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_certutil_commands.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_certutil_commands.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_certutil_commands.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_execution_from_mounted_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_execution_from_mounted_device.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_execution_from_mounted_device.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_execution_from_mounted_device.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_managedcode_host_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_access_direct_syscall.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_process_access_direct_syscall.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_access_direct_syscall.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_process_access_direct_syscall.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_creation_calltrace.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_process_creation_calltrace.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_process_creation_calltrace.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_process_creation_calltrace.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_scrobj_load.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_short_program_name.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_short_program_name.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_short_program_name.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_short_program_name.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_wmi_script.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_wmi_script.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_wmi_script.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_suspicious_zoom_child_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_system_critical_proc_abnormal_file_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_tcc_bypass_mounted_apfs_access.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_timestomp_touch.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_timestomp_touch.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_timestomp_touch.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_timestomp_touch.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unload_endpointsecurity_kext.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_ads_file_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_dir_ads.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_dir_ads.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_dir_ads.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_dllhost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_network_connection_via_dllhost.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_dllhost.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_network_connection_via_dllhost.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_network_connection_via_rundll32.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_process_network_connection.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_process_network_connection.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_process_network_connection.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_unusual_system_vp_child_program.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_via_filter_manager.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_via_filter_manager.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_via_filter_manager.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_acl_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_waf_acl_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_acl_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_waf_acl_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_waf_rule_or_rule_group_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_workfolders_control_execution.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_workfolders_control_execution.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_workfolders_control_execution.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/defense_evasion_workfolders_control_execution.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_adfind_command_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_adfind_command_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_adfind_command_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_admin_recon.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_admin_recon.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_admin_recon.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_blob_container_access_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_blob_container_access_mod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_blob_container_access_mod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_blob_container_access_mod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_command_system_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_command_system_account.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_command_system_account.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_command_system_account.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_denied_service_account_request.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_denied_service_account_request.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_denied_service_account_request.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_denied_service_account_request.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_enumerating_domain_trusts_via_nltest.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_enumerating_domain_trusts_via_nltest.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_enumerating_domain_trusts_via_nltest.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_enumerating_domain_trusts_via_nltest.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_kernel_module_enumeration.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_kernel_module_enumeration.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_kernel_module_enumeration.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_kernel_module_enumeration.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_linux_hping_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_linux_hping_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_linux_hping_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_linux_hping_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_linux_nping_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_linux_nping_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_linux_nping_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_linux_nping_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_ml_linux_system_information_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_ml_linux_system_information_discovery.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_ml_linux_system_information_discovery.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_ml_linux_system_information_discovery.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_ml_linux_system_network_configuration_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_ml_linux_system_network_configuration_discovery.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_ml_linux_system_network_configuration_discovery.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_ml_linux_system_network_configuration_discovery.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_ml_linux_system_network_connection_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_ml_linux_system_network_connection_discovery.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_ml_linux_system_network_connection_discovery.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_ml_linux_system_network_connection_discovery.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_ml_linux_system_process_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_ml_linux_system_process_discovery.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_ml_linux_system_process_discovery.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_ml_linux_system_process_discovery.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_ml_linux_system_user_discovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_ml_linux_system_user_discovery.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_ml_linux_system_user_discovery.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_ml_linux_system_user_discovery.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_net_view.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_net_view.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_net_view.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_peripheral_device.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_peripheral_device.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_peripheral_device.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_invoke_sharefinder.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_posh_invoke_sharefinder.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_invoke_sharefinder.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_posh_invoke_sharefinder.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_posh_suspicious_api_functions.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_posh_suspicious_api_functions.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_posh_suspicious_api_functions.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_post_exploitation_external_ip_lookup.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_privileged_localgroup_membership.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_privileged_localgroup_membership.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_privileged_localgroup_membership.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_remote_system_discovery_commands_windows.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_security_software_grep.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_grep.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_security_software_grep.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_security_software_wmic.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_security_software_wmic.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_security_software_wmic.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_suspicious_self_subject_review.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_suspicious_self_subject_review.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_suspicious_self_subject_review.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_suspicious_self_subject_review.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_users_domain_built_in_commands.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_users_domain_built_in_commands.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_users_domain_built_in_commands.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_users_domain_built_in_commands.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_virtual_machine_fingerprinting.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_virtual_machine_fingerprinting.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting_grep.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_virtual_machine_fingerprinting_grep.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_virtual_machine_fingerprinting_grep.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_virtual_machine_fingerprinting_grep.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_whoami_command_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/discovery_whoami_command_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/discovery_whoami_command_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint_security.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint_security.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_adversary_behavior_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/endgame_adversary_behavior_detected.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_adversary_behavior_detected.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/endgame_adversary_behavior_detected.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/endgame_malware_detected.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_detected.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/endgame_malware_detected.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/endgame_malware_prevented.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_malware_prevented.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/endgame_malware_prevented.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/endgame_ransomware_detected.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_detected.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/endgame_ransomware_detected.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/endgame_ransomware_prevented.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/endgame_ransomware_prevented.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/endgame_ransomware_prevented.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_abnormal_process_id_file_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_abnormal_process_id_file_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_abnormal_process_id_file_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_abnormal_process_id_file_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_apt_solarwinds_backdoor_child_cmd_powershell.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_apt_solarwinds_backdoor_unusual_child_processes.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_com_object_xwizard.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_com_object_xwizard.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_com_object_xwizard.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_com_object_xwizard.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_command_prompt_connecting_to_the_internet.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_command_shell_started_by_svchost.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_svchost.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_command_shell_started_by_svchost.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_unusual_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_command_shell_started_by_unusual_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_started_by_unusual_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_command_shell_started_by_unusual_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_via_rundll32.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_command_shell_via_rundll32.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_shell_via_rundll32.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_command_shell_via_rundll32.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_virtual_machine.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_command_virtual_machine.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_command_virtual_machine.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_command_virtual_machine.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_defense_evasion_electron_app_childproc_node_js.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_endgame_exploit_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_endgame_exploit_detected.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_endgame_exploit_detected.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_endgame_exploit_detected.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_endgame_exploit_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_endgame_exploit_prevented.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_endgame_exploit_prevented.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_endgame_exploit_prevented.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_enumeration_via_wmiprvse.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_enumeration_via_wmiprvse.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_enumeration_via_wmiprvse.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_from_unusual_path_cmdline.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_from_unusual_path_cmdline.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_from_unusual_path_cmdline.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_html_help_executable_program_connecting_to_the_internet.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_initial_access_suspicious_browser_childproc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_initial_access_suspicious_browser_childproc.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_initial_access_suspicious_browser_childproc.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_initial_access_suspicious_browser_childproc.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_installer_package_spawned_network_event.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_installer_package_spawned_network_event.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_installer_package_spawned_network_event.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_installer_package_spawned_network_event.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_linux_netcat_network_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_linux_netcat_network_connection.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_linux_netcat_network_connection.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_linux_netcat_network_connection.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ml_windows_anomalous_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_ml_windows_anomalous_script.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ml_windows_anomalous_script.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_ml_windows_anomalous_script.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_ms_office_written_file.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_ms_office_written_file.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_ms_office_written_file.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_pdf_written_file.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pdf_written_file.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_pdf_written_file.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_pentest_eggshell_remote_admin_tool.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_perl_tty_shell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_perl_tty_shell.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_perl_tty_shell.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_perl_tty_shell.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_posh_portable_executable.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_portable_executable.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_posh_portable_executable.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_posh_psreflect.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_posh_psreflect.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_posh_psreflect.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_process_started_from_process_id_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_process_started_from_process_id_file.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_process_started_from_process_id_file.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_process_started_from_process_id_file.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_process_started_in_shared_memory_directory.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_process_started_in_shared_memory_directory.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_process_started_in_shared_memory_directory.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_process_started_in_shared_memory_directory.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_psexec_lateral_movement_command.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_psexec_lateral_movement_command.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_psexec_lateral_movement_command.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_python_tty_shell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_python_tty_shell.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_python_tty_shell.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_python_tty_shell.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_register_server_program_connecting_to_the_internet.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_revershell_via_shell_cmd.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_revershell_via_shell_cmd.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_revershell_via_shell_cmd.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_scheduled_task_powershell_source.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scheduled_task_powershell_source.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_scheduled_task_powershell_source.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_via_automator_workflows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_script_via_automator_workflows.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_script_via_automator_workflows.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_script_via_automator_workflows.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scripting_osascript_exec_followed_by_netcon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_scripting_osascript_exec_followed_by_netcon.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_scripting_osascript_exec_followed_by_netcon.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_scripting_osascript_exec_followed_by_netcon.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shared_modules_local_sxs_dll.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_shared_modules_local_sxs_dll.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shared_modules_local_sxs_dll.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_shared_modules_local_sxs_dll.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_evasion_linux_binary.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_shell_evasion_linux_binary.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_evasion_linux_binary.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_shell_evasion_linux_binary.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_execution_via_apple_scripting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_shell_execution_via_apple_scripting.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_shell_execution_via_apple_scripting.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_shell_execution_via_apple_scripting.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_cmd_wmi.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_cmd_wmi.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_cmd_wmi.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_cmd_wmi.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_image_load_wmi_ms_office.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_jar_child_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_jar_child_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_jar_child_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_java_netcon_childproc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_java_netcon_childproc.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_java_netcon_childproc.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_java_netcon_childproc.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_pdf_reader.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_pdf_reader.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_pdf_reader.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_powershell_imgload.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_powershell_imgload.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_powershell_imgload.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_psexesvc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_psexesvc.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_suspicious_psexesvc.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_suspicious_psexesvc.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_tc_bpf_filter.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_tc_bpf_filter.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_tc_bpf_filter.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_tc_bpf_filter.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_user_exec_to_pod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_user_exec_to_pod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_user_exec_to_pod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_user_exec_to_pod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_via_compiled_html_file.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_compiled_html_file.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_via_compiled_html_file.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_via_hidden_shell_conhost.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_hidden_shell_conhost.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_via_hidden_shell_conhost.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/execution_via_xp_cmdshell_mssql_stored_procedure.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_full_network_packet_capture_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_ec2_full_network_packet_capture_detected.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_full_network_packet_capture_detected.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_ec2_full_network_packet_capture_detected.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_ec2_snapshot_change_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_vm_export_failure.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_ec2_vm_export_failure.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_vm_export_failure.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_ec2_vm_export_failure.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_gcp_logging_sink_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_gcp_logging_sink_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_gcp_logging_sink_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_gcp_logging_sink_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_microsoft_365_exchange_transport_rule_mod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_export.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_rds_snapshot_export.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_export.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_rds_snapshot_export.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_restored.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_rds_snapshot_restored.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_rds_snapshot_restored.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/exfiltration_rds_snapshot_restored.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/external_alerts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/external_alerts.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/external_alerts.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/external_alerts.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/guided_onborading_sample_rule.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/guided_onborading_sample_rule.json new file mode 100644 index 0000000000000..7c8258984e306 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/guided_onborading_sample_rule.json @@ -0,0 +1,60 @@ +{ + "author": [ + "Elastic" + ], + "description": "This rule helps you test and practice using alerts with Elastic Security as you get set up. It\u2019s not a sign of threat activity.", + "enabled": false, + "false_positives": [ + "This rule is not looking for threat activity. Disable the rule if you're already familiar with alerts." + ], + "from": "now-24h", + "index": [ + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "traces-apm*", + "winlogbeat-*", + "-*elastic-cloud-logs-*" + ], + "interval": "24h", + "language": "kuery", + "license": "Elastic License v2", + "max_signals": 1, + "name": "My First Alert", + "note": " \nThis is a test alert.\n\nThis alert does not show threat activity. Elastic created this alert to help you understand how alerts work.\n\nFor normal rules, the Investigation Guide will help analysts investigate alerts.\n\nThis alert will show once every 24 hours for each host. It is safe to disable this rule.\n", + "query": "event.kind:\"event\"\n", + "references": [ + "https://www.elastic.co/guide/en/security/current/prebuilt-rules.html" + ], + "required_fields": [ + { + "ecs": true, + "name": "event.kind", + "type": "keyword" + } + ], + "risk_score": 21, + "rule_id": "a198fbbd-9413-45ec-a269-47ae4ccf59ce", + "severity": "low", + "tags": [ + "Elastic", + "Example", + "Guided Onboarding", + "Network", + "APM", + "Windows", + "Elastic Endgame" + ], + "threshold": { + "field": [ + "host.name" + ], + "value": 1 + }, + "timestamp_override": "event.ingested", + "type": "threshold", + "version": 1 +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_attempt_to_revoke_okta_api_token.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_attempt_to_revoke_okta_api_token.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_attempt_to_revoke_okta_api_token.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_attempt_to_revoke_okta_api_token.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_aws_eventbridge_rule_disabled_or_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_aws_eventbridge_rule_disabled_or_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_aws_eventbridge_rule_disabled_or_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_aws_eventbridge_rule_disabled_or_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_azure_service_principal_credentials_added.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_azure_service_principal_credentials_added.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_azure_service_principal_credentials_added.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_azure_service_principal_credentials_added.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_backup_file_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_backup_file_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_backup_file_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_backup_file_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudtrail_logging_updated.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_cloudtrail_logging_updated.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudtrail_logging_updated.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_cloudtrail_logging_updated.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_cloudwatch_log_group_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_group_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_cloudwatch_log_group_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_cloudwatch_log_stream_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_deleting_backup_catalogs_with_wbadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_deleting_backup_catalogs_with_wbadmin.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_deleting_backup_catalogs_with_wbadmin.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_deleting_backup_catalogs_with_wbadmin.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_ec2_disable_ebs_encryption.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_ec2_disable_ebs_encryption.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_ec2_disable_ebs_encryption.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_ec2_disable_ebs_encryption.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_efs_filesystem_or_mount_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_efs_filesystem_or_mount_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_efs_filesystem_or_mount_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_efs_filesystem_or_mount_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_iam_role_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_gcp_iam_role_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_iam_role_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_gcp_iam_role_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_gcp_service_account_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_gcp_service_account_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_gcp_service_account_disabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_service_account_disabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_gcp_service_account_disabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_storage_bucket_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_gcp_storage_bucket_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_gcp_storage_bucket_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_gcp_storage_bucket_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_google_workspace_admin_role_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_google_workspace_admin_role_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_google_workspace_admin_role_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_google_workspace_admin_role_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_google_workspace_mfa_enforcement_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_google_workspace_mfa_enforcement_disabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_google_workspace_mfa_enforcement_disabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_google_workspace_mfa_enforcement_disabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_hosts_file_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_hosts_file_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_hosts_file_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_deactivate_mfa_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_iam_deactivate_mfa_device.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_deactivate_mfa_device.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_iam_deactivate_mfa_device.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_iam_group_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_iam_group_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_iam_group_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_kubernetes_pod_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_kubernetes_pod_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_kubernetes_pod_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_kubernetes_pod_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_microsoft_365_potential_ransomware_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_microsoft_365_unusual_volume_of_file_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_modification_of_boot_config.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_modification_of_boot_config.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_modification_of_boot_config.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_okta_attempt_to_deactivate_okta_application.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_okta_attempt_to_deactivate_okta_application.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_okta_attempt_to_deactivate_okta_application.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_okta_attempt_to_deactivate_okta_application.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_okta_attempt_to_delete_okta_application.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_okta_attempt_to_delete_okta_application.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_okta_attempt_to_delete_okta_application.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_okta_attempt_to_delete_okta_application.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_okta_attempt_to_modify_okta_application.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_okta_attempt_to_modify_okta_application.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_okta_attempt_to_modify_okta_application.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_okta_attempt_to_modify_okta_application.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_possible_okta_dos_attack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_possible_okta_dos_attack.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_possible_okta_dos_attack.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_possible_okta_dos_attack.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_process_kill_threshold.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_process_kill_threshold.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_process_kill_threshold.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_process_kill_threshold.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_rds_group_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_group_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_rds_group_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_instance_cluster_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_rds_instance_cluster_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_instance_cluster_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_rds_instance_cluster_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_instance_cluster_stoppage.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_rds_instance_cluster_stoppage.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_rds_instance_cluster_stoppage.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_rds_instance_cluster_stoppage.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_resource_group_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_resource_group_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_resource_group_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_resource_group_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_stop_process_service_threshold.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_stop_process_service_threshold.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_stop_process_service_threshold.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_virtual_network_device_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_virtual_network_device_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_virtual_network_device_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_virtual_network_device_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_volume_shadow_copy_deletion_via_powershell.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_wmic.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_volume_shadow_copy_deletion_via_wmic.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/impact_volume_shadow_copy_deletion_via_wmic.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/impact_volume_shadow_copy_deletion_via_wmic.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/index.ts new file mode 100644 index 0000000000000..5d2cbf2aa963f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/index.ts @@ -0,0 +1,1428 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// Auto generated file from either: +// - scripts/regen_prepackage_rules_index.sh +// - detection-rules repo using CLI command build-release +// Do not hand edit. Run script/command to regenerate package information instead + +import rule1 from './apm_403_response_to_a_post.json'; +import rule2 from './apm_405_response_method_not_allowed.json'; +import rule3 from './apm_sqlmap_user_agent.json'; +import rule4 from './collection_cloudtrail_logging_created.json'; +import rule5 from './collection_email_powershell_exchange_mailbox.json'; +import rule6 from './collection_gcp_pub_sub_subscription_creation.json'; +import rule7 from './collection_gcp_pub_sub_topic_creation.json'; +import rule8 from './collection_google_drive_ownership_transferred_via_google_workspace.json'; +import rule9 from './collection_google_workspace_custom_gmail_route_created_or_modified.json'; +import rule10 from './collection_microsoft_365_new_inbox_rule.json'; +import rule11 from './collection_posh_audio_capture.json'; +import rule12 from './collection_posh_keylogger.json'; +import rule13 from './collection_posh_screen_grabber.json'; +import rule14 from './collection_update_event_hub_auth_rule.json'; +import rule15 from './collection_winrar_encryption.json'; +import rule16 from './command_and_control_certutil_network_connection.json'; +import rule17 from './command_and_control_cobalt_strike_beacon.json'; +import rule18 from './command_and_control_cobalt_strike_default_teamserver_cert.json'; +import rule19 from './command_and_control_common_webservices.json'; +import rule20 from './command_and_control_connection_attempt_by_non_ssh_root_session.json'; +import rule21 from './command_and_control_dns_tunneling_nslookup.json'; +import rule22 from './command_and_control_download_rar_powershell_from_internet.json'; +import rule23 from './command_and_control_encrypted_channel_freesslcert.json'; +import rule24 from './command_and_control_fin7_c2_behavior.json'; +import rule25 from './command_and_control_halfbaked_beacon.json'; +import rule26 from './command_and_control_iexplore_via_com.json'; +import rule27 from './command_and_control_linux_iodine_activity.json'; +import rule28 from './command_and_control_ml_packetbeat_dns_tunneling.json'; +import rule29 from './command_and_control_ml_packetbeat_rare_dns_question.json'; +import rule30 from './command_and_control_ml_packetbeat_rare_urls.json'; +import rule31 from './command_and_control_ml_packetbeat_rare_user_agent.json'; +import rule32 from './command_and_control_nat_traversal_port_activity.json'; +import rule33 from './command_and_control_port_26_activity.json'; +import rule34 from './command_and_control_port_forwarding_added_registry.json'; +import rule35 from './command_and_control_rdp_remote_desktop_protocol_from_the_internet.json'; +import rule36 from './command_and_control_rdp_tunnel_plink.json'; +import rule37 from './command_and_control_remote_file_copy_desktopimgdownldr.json'; +import rule38 from './command_and_control_remote_file_copy_mpcmdrun.json'; +import rule39 from './command_and_control_remote_file_copy_powershell.json'; +import rule40 from './command_and_control_remote_file_copy_scripts.json'; +import rule41 from './command_and_control_sunburst_c2_activity_detected.json'; +import rule42 from './command_and_control_teamviewer_remote_file_copy.json'; +import rule43 from './command_and_control_telnet_port_activity.json'; +import rule44 from './command_and_control_tunneling_via_earthworm.json'; +import rule45 from './command_and_control_vnc_virtual_network_computing_from_the_internet.json'; +import rule46 from './command_and_control_vnc_virtual_network_computing_to_the_internet.json'; +import rule47 from './credential_access_access_to_browser_credentials_procargs.json'; +import rule48 from './credential_access_attempted_bypass_of_okta_mfa.json'; +import rule49 from './credential_access_attempts_to_brute_force_okta_user_account.json'; +import rule50 from './credential_access_aws_iam_assume_role_brute_force.json'; +import rule51 from './credential_access_azure_full_network_packet_capture_detected.json'; +import rule52 from './credential_access_bruteforce_admin_account.json'; +import rule53 from './credential_access_bruteforce_multiple_logon_failure_followed_by_success.json'; +import rule54 from './credential_access_bruteforce_multiple_logon_failure_same_srcip.json'; +import rule55 from './credential_access_bruteforce_passowrd_guessing.json'; +import rule56 from './credential_access_cmdline_dump_tool.json'; +import rule57 from './credential_access_collection_sensitive_files.json'; +import rule58 from './credential_access_cookies_chromium_browsers_debugging.json'; +import rule59 from './credential_access_copy_ntds_sam_volshadowcp_cmdline.json'; +import rule60 from './credential_access_credential_dumping_msbuild.json'; +import rule61 from './credential_access_credentials_keychains.json'; +import rule62 from './credential_access_dcsync_replication_rights.json'; +import rule63 from './credential_access_disable_kerberos_preauth.json'; +import rule64 from './credential_access_domain_backup_dpapi_private_keys.json'; +import rule65 from './credential_access_dump_registry_hives.json'; +import rule66 from './credential_access_dumping_hashes_bi_cmds.json'; +import rule67 from './credential_access_dumping_keychain_security.json'; +import rule68 from './credential_access_endgame_cred_dumping_detected.json'; +import rule69 from './credential_access_endgame_cred_dumping_prevented.json'; +import rule70 from './credential_access_generic_localdumps.json'; +import rule71 from './credential_access_iam_user_addition_to_group.json'; +import rule72 from './credential_access_iis_apppoolsa_pwd_appcmd.json'; +import rule73 from './credential_access_iis_connectionstrings_dumping.json'; +import rule74 from './credential_access_kerberoasting_unusual_process.json'; +import rule75 from './credential_access_kerberosdump_kcc.json'; +import rule76 from './credential_access_key_vault_modified.json'; +import rule77 from './credential_access_keychain_pwd_retrieval_security_cmd.json'; +import rule78 from './credential_access_lsass_handle_via_malseclogon.json'; +import rule79 from './credential_access_lsass_memdump_file_created.json'; +import rule80 from './credential_access_lsass_memdump_handle_access.json'; +import rule81 from './credential_access_mfa_push_brute_force.json'; +import rule82 from './credential_access_microsoft_365_brute_force_user_account_attempt.json'; +import rule83 from './credential_access_microsoft_365_potential_password_spraying_attack.json'; +import rule84 from './credential_access_mimikatz_memssp_default_logs.json'; +import rule85 from './credential_access_mimikatz_powershell_module.json'; +import rule86 from './credential_access_mitm_localhost_webproxy.json'; +import rule87 from './credential_access_ml_auth_spike_in_failed_logon_events.json'; +import rule88 from './credential_access_ml_auth_spike_in_logon_events.json'; +import rule89 from './credential_access_ml_auth_spike_in_logon_events_from_a_source_ip.json'; +import rule90 from './credential_access_ml_linux_anomalous_metadata_process.json'; +import rule91 from './credential_access_ml_linux_anomalous_metadata_user.json'; +import rule92 from './credential_access_ml_suspicious_login_activity.json'; +import rule93 from './credential_access_ml_windows_anomalous_metadata_process.json'; +import rule94 from './credential_access_ml_windows_anomalous_metadata_user.json'; +import rule95 from './credential_access_mod_wdigest_security_provider.json'; +import rule96 from './credential_access_moving_registry_hive_via_smb.json'; +import rule97 from './credential_access_okta_brute_force_or_password_spraying.json'; +import rule98 from './credential_access_persistence_network_logon_provider_modification.json'; +import rule99 from './credential_access_posh_minidump.json'; +import rule100 from './credential_access_posh_request_ticket.json'; +import rule101 from './credential_access_potential_linux_ssh_bruteforce.json'; +import rule102 from './credential_access_potential_linux_ssh_bruteforce_root.json'; +import rule103 from './credential_access_potential_lsa_memdump_via_mirrordump.json'; +import rule104 from './credential_access_potential_macos_ssh_bruteforce.json'; +import rule105 from './credential_access_promt_for_pwd_via_osascript.json'; +import rule106 from './credential_access_relay_ntlm_auth_via_http_spoolss.json'; +import rule107 from './credential_access_remote_sam_secretsdump.json'; +import rule108 from './credential_access_root_console_failure_brute_force.json'; +import rule109 from './credential_access_saved_creds_vault_winlog.json'; +import rule110 from './credential_access_saved_creds_vaultcmd.json'; +import rule111 from './credential_access_secretsmanager_getsecretvalue.json'; +import rule112 from './credential_access_seenabledelegationprivilege_assigned_to_user.json'; +import rule113 from './credential_access_shadow_credentials.json'; +import rule114 from './credential_access_spn_attribute_modified.json'; +import rule115 from './credential_access_ssh_backdoor_log.json'; +import rule116 from './credential_access_storage_account_key_regenerated.json'; +import rule117 from './credential_access_suspicious_comsvcs_imageload.json'; +import rule118 from './credential_access_suspicious_lsass_access_memdump.json'; +import rule119 from './credential_access_suspicious_lsass_access_via_snapshot.json'; +import rule120 from './credential_access_suspicious_winreg_access_via_sebackup_priv.json'; +import rule121 from './credential_access_symbolic_link_to_shadow_copy_created.json'; +import rule122 from './credential_access_systemkey_dumping.json'; +import rule123 from './credential_access_user_excessive_sso_logon_errors.json'; +import rule124 from './credential_access_user_impersonation_access.json'; +import rule125 from './credential_access_via_snapshot_lsass_clone_creation.json'; +import rule126 from './defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json'; +import rule127 from './defense_evasion_agent_spoofing_mismatched_id.json'; +import rule128 from './defense_evasion_agent_spoofing_multiple_hosts.json'; +import rule129 from './defense_evasion_amsienable_key_mod.json'; +import rule130 from './defense_evasion_apple_softupdates_modification.json'; +import rule131 from './defense_evasion_application_removed_from_blocklist_in_google_workspace.json'; +import rule132 from './defense_evasion_attempt_del_quarantine_attrib.json'; +import rule133 from './defense_evasion_attempt_to_deactivate_okta_network_zone.json'; +import rule134 from './defense_evasion_attempt_to_delete_okta_network_zone.json'; +import rule135 from './defense_evasion_attempt_to_disable_gatekeeper.json'; +import rule136 from './defense_evasion_attempt_to_disable_syslog_service.json'; +import rule137 from './defense_evasion_azure_application_credential_modification.json'; +import rule138 from './defense_evasion_azure_automation_runbook_deleted.json'; +import rule139 from './defense_evasion_azure_blob_permissions_modified.json'; +import rule140 from './defense_evasion_azure_diagnostic_settings_deletion.json'; +import rule141 from './defense_evasion_azure_service_principal_addition.json'; +import rule142 from './defense_evasion_base16_or_base32_encoding_or_decoding_activity.json'; +import rule143 from './defense_evasion_chattr_immutable_file.json'; +import rule144 from './defense_evasion_clearing_windows_console_history.json'; +import rule145 from './defense_evasion_clearing_windows_event_logs.json'; +import rule146 from './defense_evasion_clearing_windows_security_logs.json'; +import rule147 from './defense_evasion_cloudtrail_logging_deleted.json'; +import rule148 from './defense_evasion_cloudtrail_logging_suspended.json'; +import rule149 from './defense_evasion_cloudwatch_alarm_deletion.json'; +import rule150 from './defense_evasion_config_service_rule_deletion.json'; +import rule151 from './defense_evasion_configuration_recorder_stopped.json'; +import rule152 from './defense_evasion_create_mod_root_certificate.json'; +import rule153 from './defense_evasion_cve_2020_0601.json'; +import rule154 from './defense_evasion_defender_disabled_via_registry.json'; +import rule155 from './defense_evasion_defender_exclusion_via_powershell.json'; +import rule156 from './defense_evasion_delete_volume_usn_journal_with_fsutil.json'; +import rule157 from './defense_evasion_deleting_websvr_access_logs.json'; +import rule158 from './defense_evasion_deletion_of_bash_command_line_history.json'; +import rule159 from './defense_evasion_disable_posh_scriptblocklogging.json'; +import rule160 from './defense_evasion_disable_selinux_attempt.json'; +import rule161 from './defense_evasion_disable_windows_firewall_rules_with_netsh.json'; +import rule162 from './defense_evasion_disabling_windows_defender_powershell.json'; +import rule163 from './defense_evasion_disabling_windows_logs.json'; +import rule164 from './defense_evasion_dns_over_https_enabled.json'; +import rule165 from './defense_evasion_domain_added_to_google_workspace_trusted_domains.json'; +import rule166 from './defense_evasion_dotnet_compiler_parent_process.json'; +import rule167 from './defense_evasion_ec2_flow_log_deletion.json'; +import rule168 from './defense_evasion_ec2_network_acl_deletion.json'; +import rule169 from './defense_evasion_elastic_agent_service_terminated.json'; +import rule170 from './defense_evasion_elasticache_security_group_creation.json'; +import rule171 from './defense_evasion_elasticache_security_group_modified_or_deleted.json'; +import rule172 from './defense_evasion_enable_inbound_rdp_with_netsh.json'; +import rule173 from './defense_evasion_enable_network_discovery_with_netsh.json'; +import rule174 from './defense_evasion_event_hub_deletion.json'; +import rule175 from './defense_evasion_execution_control_panel_suspicious_args.json'; +import rule176 from './defense_evasion_execution_lolbas_wuauclt.json'; +import rule177 from './defense_evasion_execution_msbuild_started_by_office_app.json'; +import rule178 from './defense_evasion_execution_msbuild_started_by_script.json'; +import rule179 from './defense_evasion_execution_msbuild_started_by_system_process.json'; +import rule180 from './defense_evasion_execution_msbuild_started_renamed.json'; +import rule181 from './defense_evasion_execution_msbuild_started_unusal_process.json'; +import rule182 from './defense_evasion_execution_suspicious_explorer_winword.json'; +import rule183 from './defense_evasion_execution_windefend_unusual_path.json'; +import rule184 from './defense_evasion_file_creation_mult_extension.json'; +import rule185 from './defense_evasion_file_deletion_via_shred.json'; +import rule186 from './defense_evasion_file_mod_writable_dir.json'; +import rule187 from './defense_evasion_firewall_policy_deletion.json'; +import rule188 from './defense_evasion_from_unusual_directory.json'; +import rule189 from './defense_evasion_frontdoor_firewall_policy_deletion.json'; +import rule190 from './defense_evasion_gcp_firewall_rule_created.json'; +import rule191 from './defense_evasion_gcp_firewall_rule_deleted.json'; +import rule192 from './defense_evasion_gcp_firewall_rule_modified.json'; +import rule193 from './defense_evasion_gcp_logging_bucket_deletion.json'; +import rule194 from './defense_evasion_gcp_logging_sink_deletion.json'; +import rule195 from './defense_evasion_gcp_pub_sub_subscription_deletion.json'; +import rule196 from './defense_evasion_gcp_pub_sub_topic_deletion.json'; +import rule197 from './defense_evasion_gcp_storage_bucket_configuration_modified.json'; +import rule198 from './defense_evasion_gcp_storage_bucket_permissions_modified.json'; +import rule199 from './defense_evasion_gcp_virtual_private_cloud_network_deleted.json'; +import rule200 from './defense_evasion_gcp_virtual_private_cloud_route_created.json'; +import rule201 from './defense_evasion_gcp_virtual_private_cloud_route_deleted.json'; +import rule202 from './defense_evasion_google_workspace_bitlocker_setting_disabled.json'; +import rule203 from './defense_evasion_google_workspace_restrictions_for_google_marketplace_changed_to_allow_any_app.json'; +import rule204 from './defense_evasion_guardduty_detector_deletion.json'; +import rule205 from './defense_evasion_hidden_file_dir_tmp.json'; +import rule206 from './defense_evasion_hidden_shared_object.json'; +import rule207 from './defense_evasion_hide_encoded_executable_registry.json'; +import rule208 from './defense_evasion_iis_httplogging_disabled.json'; +import rule209 from './defense_evasion_injection_msbuild.json'; +import rule210 from './defense_evasion_install_root_certificate.json'; +import rule211 from './defense_evasion_installutil_beacon.json'; +import rule212 from './defense_evasion_kernel_module_removal.json'; +import rule213 from './defense_evasion_kubernetes_events_deleted.json'; +import rule214 from './defense_evasion_log_files_deleted.json'; +import rule215 from './defense_evasion_masquerading_as_elastic_endpoint_process.json'; +import rule216 from './defense_evasion_masquerading_renamed_autoit.json'; +import rule217 from './defense_evasion_masquerading_suspicious_werfault_childproc.json'; +import rule218 from './defense_evasion_masquerading_trusted_directory.json'; +import rule219 from './defense_evasion_masquerading_werfault.json'; +import rule220 from './defense_evasion_microsoft_365_exchange_dlp_policy_removed.json'; +import rule221 from './defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json'; +import rule222 from './defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json'; +import rule223 from './defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json'; +import rule224 from './defense_evasion_microsoft_365_mailboxauditbypassassociation.json'; +import rule225 from './defense_evasion_microsoft_defender_tampering.json'; +import rule226 from './defense_evasion_misc_lolbin_connecting_to_the_internet.json'; +import rule227 from './defense_evasion_modify_environment_launchctl.json'; +import rule228 from './defense_evasion_ms_office_suspicious_regmod.json'; +import rule229 from './defense_evasion_msbuild_making_network_connections.json'; +import rule230 from './defense_evasion_mshta_beacon.json'; +import rule231 from './defense_evasion_msxsl_network.json'; +import rule232 from './defense_evasion_network_connection_from_windows_binary.json'; +import rule233 from './defense_evasion_network_watcher_deletion.json'; +import rule234 from './defense_evasion_okta_attempt_to_deactivate_okta_policy.json'; +import rule235 from './defense_evasion_okta_attempt_to_deactivate_okta_policy_rule.json'; +import rule236 from './defense_evasion_okta_attempt_to_delete_okta_policy.json'; +import rule237 from './defense_evasion_okta_attempt_to_delete_okta_policy_rule.json'; +import rule238 from './defense_evasion_okta_attempt_to_modify_okta_network_zone.json'; +import rule239 from './defense_evasion_okta_attempt_to_modify_okta_policy.json'; +import rule240 from './defense_evasion_okta_attempt_to_modify_okta_policy_rule.json'; +import rule241 from './defense_evasion_parent_process_pid_spoofing.json'; +import rule242 from './defense_evasion_persistence_temp_scheduled_task.json'; +import rule243 from './defense_evasion_posh_assembly_load.json'; +import rule244 from './defense_evasion_posh_compressed.json'; +import rule245 from './defense_evasion_posh_process_injection.json'; +import rule246 from './defense_evasion_potential_processherpaderping.json'; +import rule247 from './defense_evasion_powershell_windows_firewall_disabled.json'; +import rule248 from './defense_evasion_privacy_controls_tcc_database_modification.json'; +import rule249 from './defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json'; +import rule250 from './defense_evasion_process_termination_followed_by_deletion.json'; +import rule251 from './defense_evasion_proxy_execution_via_msdt.json'; +import rule252 from './defense_evasion_rundll32_no_arguments.json'; +import rule253 from './defense_evasion_s3_bucket_configuration_deletion.json'; +import rule254 from './defense_evasion_safari_config_change.json'; +import rule255 from './defense_evasion_sandboxed_office_app_suspicious_zip_file.json'; +import rule256 from './defense_evasion_scheduledjobs_at_protocol_enabled.json'; +import rule257 from './defense_evasion_sdelete_like_filename_rename.json'; +import rule258 from './defense_evasion_sip_provider_mod.json'; +import rule259 from './defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json'; +import rule260 from './defense_evasion_suppression_rule_created.json'; +import rule261 from './defense_evasion_suspicious_certutil_commands.json'; +import rule262 from './defense_evasion_suspicious_execution_from_mounted_device.json'; +import rule263 from './defense_evasion_suspicious_managedcode_host_process.json'; +import rule264 from './defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json'; +import rule265 from './defense_evasion_suspicious_process_access_direct_syscall.json'; +import rule266 from './defense_evasion_suspicious_process_creation_calltrace.json'; +import rule267 from './defense_evasion_suspicious_scrobj_load.json'; +import rule268 from './defense_evasion_suspicious_short_program_name.json'; +import rule269 from './defense_evasion_suspicious_wmi_script.json'; +import rule270 from './defense_evasion_suspicious_zoom_child_process.json'; +import rule271 from './defense_evasion_system_critical_proc_abnormal_file_activity.json'; +import rule272 from './defense_evasion_tcc_bypass_mounted_apfs_access.json'; +import rule273 from './defense_evasion_timestomp_touch.json'; +import rule274 from './defense_evasion_unload_endpointsecurity_kext.json'; +import rule275 from './defense_evasion_unusual_ads_file_creation.json'; +import rule276 from './defense_evasion_unusual_dir_ads.json'; +import rule277 from './defense_evasion_unusual_network_connection_via_dllhost.json'; +import rule278 from './defense_evasion_unusual_network_connection_via_rundll32.json'; +import rule279 from './defense_evasion_unusual_process_network_connection.json'; +import rule280 from './defense_evasion_unusual_system_vp_child_program.json'; +import rule281 from './defense_evasion_via_filter_manager.json'; +import rule282 from './defense_evasion_waf_acl_deletion.json'; +import rule283 from './defense_evasion_waf_rule_or_rule_group_deletion.json'; +import rule284 from './defense_evasion_workfolders_control_execution.json'; +import rule285 from './discovery_adfind_command_activity.json'; +import rule286 from './discovery_admin_recon.json'; +import rule287 from './discovery_blob_container_access_mod.json'; +import rule288 from './discovery_command_system_account.json'; +import rule289 from './discovery_denied_service_account_request.json'; +import rule290 from './discovery_enumerating_domain_trusts_via_nltest.json'; +import rule291 from './discovery_kernel_module_enumeration.json'; +import rule292 from './discovery_linux_hping_activity.json'; +import rule293 from './discovery_linux_nping_activity.json'; +import rule294 from './discovery_ml_linux_system_information_discovery.json'; +import rule295 from './discovery_ml_linux_system_network_configuration_discovery.json'; +import rule296 from './discovery_ml_linux_system_network_connection_discovery.json'; +import rule297 from './discovery_ml_linux_system_process_discovery.json'; +import rule298 from './discovery_ml_linux_system_user_discovery.json'; +import rule299 from './discovery_net_view.json'; +import rule300 from './discovery_peripheral_device.json'; +import rule301 from './discovery_posh_invoke_sharefinder.json'; +import rule302 from './discovery_posh_suspicious_api_functions.json'; +import rule303 from './discovery_post_exploitation_external_ip_lookup.json'; +import rule304 from './discovery_privileged_localgroup_membership.json'; +import rule305 from './discovery_remote_system_discovery_commands_windows.json'; +import rule306 from './discovery_security_software_grep.json'; +import rule307 from './discovery_security_software_wmic.json'; +import rule308 from './discovery_suspicious_self_subject_review.json'; +import rule309 from './discovery_users_domain_built_in_commands.json'; +import rule310 from './discovery_virtual_machine_fingerprinting.json'; +import rule311 from './discovery_virtual_machine_fingerprinting_grep.json'; +import rule312 from './discovery_whoami_command_activity.json'; +import rule313 from './elastic_endpoint_security.json'; +import rule314 from './endgame_adversary_behavior_detected.json'; +import rule315 from './endgame_malware_detected.json'; +import rule316 from './endgame_malware_prevented.json'; +import rule317 from './endgame_ransomware_detected.json'; +import rule318 from './endgame_ransomware_prevented.json'; +import rule319 from './execution_abnormal_process_id_file_created.json'; +import rule320 from './execution_apt_solarwinds_backdoor_child_cmd_powershell.json'; +import rule321 from './execution_apt_solarwinds_backdoor_unusual_child_processes.json'; +import rule322 from './execution_com_object_xwizard.json'; +import rule323 from './execution_command_prompt_connecting_to_the_internet.json'; +import rule324 from './execution_command_shell_started_by_svchost.json'; +import rule325 from './execution_command_shell_started_by_unusual_process.json'; +import rule326 from './execution_command_shell_via_rundll32.json'; +import rule327 from './execution_command_virtual_machine.json'; +import rule328 from './execution_defense_evasion_electron_app_childproc_node_js.json'; +import rule329 from './execution_endgame_exploit_detected.json'; +import rule330 from './execution_endgame_exploit_prevented.json'; +import rule331 from './execution_enumeration_via_wmiprvse.json'; +import rule332 from './execution_from_unusual_path_cmdline.json'; +import rule333 from './execution_html_help_executable_program_connecting_to_the_internet.json'; +import rule334 from './execution_initial_access_suspicious_browser_childproc.json'; +import rule335 from './execution_installer_package_spawned_network_event.json'; +import rule336 from './execution_linux_netcat_network_connection.json'; +import rule337 from './execution_ml_windows_anomalous_script.json'; +import rule338 from './execution_ms_office_written_file.json'; +import rule339 from './execution_pdf_written_file.json'; +import rule340 from './execution_pentest_eggshell_remote_admin_tool.json'; +import rule341 from './execution_perl_tty_shell.json'; +import rule342 from './execution_posh_portable_executable.json'; +import rule343 from './execution_posh_psreflect.json'; +import rule344 from './execution_process_started_from_process_id_file.json'; +import rule345 from './execution_process_started_in_shared_memory_directory.json'; +import rule346 from './execution_psexec_lateral_movement_command.json'; +import rule347 from './execution_python_tty_shell.json'; +import rule348 from './execution_register_server_program_connecting_to_the_internet.json'; +import rule349 from './execution_revershell_via_shell_cmd.json'; +import rule350 from './execution_scheduled_task_powershell_source.json'; +import rule351 from './execution_script_via_automator_workflows.json'; +import rule352 from './execution_scripting_osascript_exec_followed_by_netcon.json'; +import rule353 from './execution_shared_modules_local_sxs_dll.json'; +import rule354 from './execution_shell_evasion_linux_binary.json'; +import rule355 from './execution_shell_execution_via_apple_scripting.json'; +import rule356 from './execution_suspicious_cmd_wmi.json'; +import rule357 from './execution_suspicious_image_load_wmi_ms_office.json'; +import rule358 from './execution_suspicious_jar_child_process.json'; +import rule359 from './execution_suspicious_java_netcon_childproc.json'; +import rule360 from './execution_suspicious_pdf_reader.json'; +import rule361 from './execution_suspicious_powershell_imgload.json'; +import rule362 from './execution_suspicious_psexesvc.json'; +import rule363 from './execution_tc_bpf_filter.json'; +import rule364 from './execution_user_exec_to_pod.json'; +import rule365 from './execution_via_compiled_html_file.json'; +import rule366 from './execution_via_hidden_shell_conhost.json'; +import rule367 from './execution_via_xp_cmdshell_mssql_stored_procedure.json'; +import rule368 from './exfiltration_ec2_full_network_packet_capture_detected.json'; +import rule369 from './exfiltration_ec2_snapshot_change_activity.json'; +import rule370 from './exfiltration_ec2_vm_export_failure.json'; +import rule371 from './exfiltration_gcp_logging_sink_modification.json'; +import rule372 from './exfiltration_microsoft_365_exchange_transport_rule_creation.json'; +import rule373 from './exfiltration_microsoft_365_exchange_transport_rule_mod.json'; +import rule374 from './exfiltration_rds_snapshot_export.json'; +import rule375 from './exfiltration_rds_snapshot_restored.json'; +import rule376 from './external_alerts.json'; +import rule377 from './guided_onborading_sample_rule.json'; +import rule378 from './impact_attempt_to_revoke_okta_api_token.json'; +import rule379 from './impact_aws_eventbridge_rule_disabled_or_deleted.json'; +import rule380 from './impact_azure_service_principal_credentials_added.json'; +import rule381 from './impact_backup_file_deletion.json'; +import rule382 from './impact_cloudtrail_logging_updated.json'; +import rule383 from './impact_cloudwatch_log_group_deletion.json'; +import rule384 from './impact_cloudwatch_log_stream_deletion.json'; +import rule385 from './impact_deleting_backup_catalogs_with_wbadmin.json'; +import rule386 from './impact_ec2_disable_ebs_encryption.json'; +import rule387 from './impact_efs_filesystem_or_mount_deleted.json'; +import rule388 from './impact_gcp_iam_role_deletion.json'; +import rule389 from './impact_gcp_service_account_deleted.json'; +import rule390 from './impact_gcp_service_account_disabled.json'; +import rule391 from './impact_gcp_storage_bucket_deleted.json'; +import rule392 from './impact_google_workspace_admin_role_deletion.json'; +import rule393 from './impact_google_workspace_mfa_enforcement_disabled.json'; +import rule394 from './impact_hosts_file_modified.json'; +import rule395 from './impact_iam_deactivate_mfa_device.json'; +import rule396 from './impact_iam_group_deletion.json'; +import rule397 from './impact_kubernetes_pod_deleted.json'; +import rule398 from './impact_microsoft_365_potential_ransomware_activity.json'; +import rule399 from './impact_microsoft_365_unusual_volume_of_file_deletion.json'; +import rule400 from './impact_modification_of_boot_config.json'; +import rule401 from './impact_okta_attempt_to_deactivate_okta_application.json'; +import rule402 from './impact_okta_attempt_to_delete_okta_application.json'; +import rule403 from './impact_okta_attempt_to_modify_okta_application.json'; +import rule404 from './impact_possible_okta_dos_attack.json'; +import rule405 from './impact_process_kill_threshold.json'; +import rule406 from './impact_rds_group_deletion.json'; +import rule407 from './impact_rds_instance_cluster_deletion.json'; +import rule408 from './impact_rds_instance_cluster_stoppage.json'; +import rule409 from './impact_resource_group_deletion.json'; +import rule410 from './impact_stop_process_service_threshold.json'; +import rule411 from './impact_virtual_network_device_modified.json'; +import rule412 from './impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json'; +import rule413 from './impact_volume_shadow_copy_deletion_via_powershell.json'; +import rule414 from './impact_volume_shadow_copy_deletion_via_wmic.json'; +import rule415 from './initial_access_anonymous_request_authorized.json'; +import rule416 from './initial_access_azure_active_directory_high_risk_signin.json'; +import rule417 from './initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json'; +import rule418 from './initial_access_azure_active_directory_powershell_signin.json'; +import rule419 from './initial_access_consent_grant_attack_via_azure_registered_application.json'; +import rule420 from './initial_access_console_login_root.json'; +import rule421 from './initial_access_evasion_suspicious_htm_file_creation.json'; +import rule422 from './initial_access_external_guest_user_invite.json'; +import rule423 from './initial_access_gcp_iam_custom_role_creation.json'; +import rule424 from './initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json'; +import rule425 from './initial_access_microsoft_365_exchange_anti_phish_rule_mod.json'; +import rule426 from './initial_access_microsoft_365_exchange_safelinks_disabled.json'; +import rule427 from './initial_access_microsoft_365_user_restricted_from_sending_email.json'; +import rule428 from './initial_access_ml_auth_rare_hour_for_a_user_to_logon.json'; +import rule429 from './initial_access_ml_auth_rare_source_ip_for_a_user.json'; +import rule430 from './initial_access_ml_auth_rare_user_logon.json'; +import rule431 from './initial_access_ml_linux_anomalous_user_name.json'; +import rule432 from './initial_access_ml_windows_anomalous_user_name.json'; +import rule433 from './initial_access_ml_windows_rare_user_type10_remote_login.json'; +import rule434 from './initial_access_o365_user_reported_phish_malware.json'; +import rule435 from './initial_access_okta_user_attempted_unauthorized_access.json'; +import rule436 from './initial_access_password_recovery.json'; +import rule437 from './initial_access_rpc_remote_procedure_call_from_the_internet.json'; +import rule438 from './initial_access_rpc_remote_procedure_call_to_the_internet.json'; +import rule439 from './initial_access_script_executing_powershell.json'; +import rule440 from './initial_access_scripts_process_started_via_wmi.json'; +import rule441 from './initial_access_smb_windows_file_sharing_activity_to_the_internet.json'; +import rule442 from './initial_access_suspicious_activity_reported_by_okta_user.json'; +import rule443 from './initial_access_suspicious_mac_ms_office_child_process.json'; +import rule444 from './initial_access_suspicious_ms_exchange_files.json'; +import rule445 from './initial_access_suspicious_ms_exchange_process.json'; +import rule446 from './initial_access_suspicious_ms_exchange_worker_child_process.json'; +import rule447 from './initial_access_suspicious_ms_office_child_process.json'; +import rule448 from './initial_access_suspicious_ms_outlook_child_process.json'; +import rule449 from './initial_access_unsecure_elasticsearch_node.json'; +import rule450 from './initial_access_unusual_dns_service_children.json'; +import rule451 from './initial_access_unusual_dns_service_file_writes.json'; +import rule452 from './initial_access_via_explorer_suspicious_child_parent_args.json'; +import rule453 from './initial_access_via_system_manager.json'; +import rule454 from './initial_access_zoom_meeting_with_no_passcode.json'; +import rule455 from './lateral_movement_cmd_service.json'; +import rule456 from './lateral_movement_credential_access_kerberos_bifrostconsole.json'; +import rule457 from './lateral_movement_dcom_hta.json'; +import rule458 from './lateral_movement_dcom_mmc20.json'; +import rule459 from './lateral_movement_dcom_shellwindow_shellbrowserwindow.json'; +import rule460 from './lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json'; +import rule461 from './lateral_movement_direct_outbound_smb_connection.json'; +import rule462 from './lateral_movement_dns_server_overflow.json'; +import rule463 from './lateral_movement_evasion_rdp_shadowing.json'; +import rule464 from './lateral_movement_executable_tool_transfer_smb.json'; +import rule465 from './lateral_movement_execution_from_tsclient_mup.json'; +import rule466 from './lateral_movement_execution_via_file_shares_sequence.json'; +import rule467 from './lateral_movement_incoming_winrm_shell_execution.json'; +import rule468 from './lateral_movement_incoming_wmi.json'; +import rule469 from './lateral_movement_malware_uploaded_onedrive.json'; +import rule470 from './lateral_movement_malware_uploaded_sharepoint.json'; +import rule471 from './lateral_movement_mount_hidden_or_webdav_share_net.json'; +import rule472 from './lateral_movement_mounting_smb_share.json'; +import rule473 from './lateral_movement_powershell_remoting_target.json'; +import rule474 from './lateral_movement_rdp_enabled_registry.json'; +import rule475 from './lateral_movement_rdp_sharprdp_target.json'; +import rule476 from './lateral_movement_remote_file_copy_hidden_share.json'; +import rule477 from './lateral_movement_remote_services.json'; +import rule478 from './lateral_movement_remote_ssh_login_enabled.json'; +import rule479 from './lateral_movement_remote_task_creation_winlog.json'; +import rule480 from './lateral_movement_scheduled_task_target.json'; +import rule481 from './lateral_movement_service_control_spawned_script_int.json'; +import rule482 from './lateral_movement_suspicious_rdp_client_imageload.json'; +import rule483 from './lateral_movement_telnet_network_activity_external.json'; +import rule484 from './lateral_movement_telnet_network_activity_internal.json'; +import rule485 from './lateral_movement_via_startup_folder_rdp_smb.json'; +import rule486 from './lateral_movement_vpn_connection_attempt.json'; +import rule487 from './ml_cloudtrail_error_message_spike.json'; +import rule488 from './ml_cloudtrail_rare_error_code.json'; +import rule489 from './ml_cloudtrail_rare_method_by_city.json'; +import rule490 from './ml_cloudtrail_rare_method_by_country.json'; +import rule491 from './ml_cloudtrail_rare_method_by_user.json'; +import rule492 from './ml_high_count_network_denies.json'; +import rule493 from './ml_high_count_network_events.json'; +import rule494 from './ml_linux_anomalous_network_activity.json'; +import rule495 from './ml_linux_anomalous_network_port_activity.json'; +import rule496 from './ml_packetbeat_rare_server_domain.json'; +import rule497 from './ml_rare_destination_country.json'; +import rule498 from './ml_spike_in_traffic_to_a_country.json'; +import rule499 from './ml_windows_anomalous_network_activity.json'; +import rule500 from './okta_threat_detected_by_okta_threatinsight.json'; +import rule501 from './persistence_account_creation_hide_at_logon.json'; +import rule502 from './persistence_ad_adminsdholder.json'; +import rule503 from './persistence_administrator_privileges_assigned_to_okta_group.json'; +import rule504 from './persistence_administrator_role_assigned_to_okta_user.json'; +import rule505 from './persistence_adobe_hijack_persistence.json'; +import rule506 from './persistence_app_compat_shim.json'; +import rule507 from './persistence_appcertdlls_registry.json'; +import rule508 from './persistence_appinitdlls_registry.json'; +import rule509 from './persistence_application_added_to_google_workspace_domain.json'; +import rule510 from './persistence_attempt_to_create_okta_api_token.json'; +import rule511 from './persistence_attempt_to_deactivate_mfa_for_okta_user_account.json'; +import rule512 from './persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json'; +import rule513 from './persistence_azure_automation_account_created.json'; +import rule514 from './persistence_azure_automation_runbook_created_or_modified.json'; +import rule515 from './persistence_azure_automation_webhook_created.json'; +import rule516 from './persistence_azure_conditional_access_policy_modified.json'; +import rule517 from './persistence_azure_global_administrator_role_assigned.json'; +import rule518 from './persistence_azure_pim_user_added_global_admin.json'; +import rule519 from './persistence_azure_privileged_identity_management_role_modified.json'; +import rule520 from './persistence_chkconfig_service_add.json'; +import rule521 from './persistence_creation_change_launch_agents_file.json'; +import rule522 from './persistence_creation_hidden_login_item_osascript.json'; +import rule523 from './persistence_creation_modif_launch_deamon_sequence.json'; +import rule524 from './persistence_credential_access_authorization_plugin_creation.json'; +import rule525 from './persistence_credential_access_modify_auth_module_or_config.json'; +import rule526 from './persistence_credential_access_modify_ssh_binaries.json'; +import rule527 from './persistence_crontab_creation.json'; +import rule528 from './persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json'; +import rule529 from './persistence_directory_services_plugins_modification.json'; +import rule530 from './persistence_docker_shortcuts_plist_modification.json'; +import rule531 from './persistence_dontexpirepasswd_account.json'; +import rule532 from './persistence_dynamic_linker_backup.json'; +import rule533 from './persistence_ec2_network_acl_creation.json'; +import rule534 from './persistence_ec2_security_group_configuration_change_detection.json'; +import rule535 from './persistence_emond_rules_file_creation.json'; +import rule536 from './persistence_emond_rules_process_execution.json'; +import rule537 from './persistence_enable_root_account.json'; +import rule538 from './persistence_etc_file_creation.json'; +import rule539 from './persistence_evasion_hidden_launch_agent_deamon_creation.json'; +import rule540 from './persistence_evasion_hidden_local_account_creation.json'; +import rule541 from './persistence_evasion_registry_ifeo_injection.json'; +import rule542 from './persistence_evasion_registry_startup_shell_folder_modified.json'; +import rule543 from './persistence_exchange_suspicious_mailbox_right_delegation.json'; +import rule544 from './persistence_exposed_service_created_with_type_nodeport.json'; +import rule545 from './persistence_finder_sync_plugin_pluginkit.json'; +import rule546 from './persistence_folder_action_scripts_runtime.json'; +import rule547 from './persistence_gcp_iam_service_account_key_deletion.json'; +import rule548 from './persistence_gcp_key_created_for_service_account.json'; +import rule549 from './persistence_gcp_service_account_created.json'; +import rule550 from './persistence_google_workspace_2sv_policy_disabled.json'; +import rule551 from './persistence_google_workspace_admin_role_assigned_to_user.json'; +import rule552 from './persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json'; +import rule553 from './persistence_google_workspace_custom_admin_role_created.json'; +import rule554 from './persistence_google_workspace_policy_modified.json'; +import rule555 from './persistence_google_workspace_role_modified.json'; +import rule556 from './persistence_google_workspace_user_group_access_modified_to_allow_external_access.json'; +import rule557 from './persistence_google_workspace_user_organizational_unit_changed.json'; +import rule558 from './persistence_gpo_schtask_service_creation.json'; +import rule559 from './persistence_iam_group_creation.json'; +import rule560 from './persistence_insmod_kernel_module_load.json'; +import rule561 from './persistence_kde_autostart_modification.json'; +import rule562 from './persistence_local_scheduled_job_creation.json'; +import rule563 from './persistence_local_scheduled_task_creation.json'; +import rule564 from './persistence_local_scheduled_task_scripting.json'; +import rule565 from './persistence_login_logout_hooks_defaults.json'; +import rule566 from './persistence_loginwindow_plist_modification.json'; +import rule567 from './persistence_mfa_disabled_for_azure_user.json'; +import rule568 from './persistence_mfa_disabled_for_google_workspace_organization.json'; +import rule569 from './persistence_microsoft_365_exchange_dkim_signing_config_disabled.json'; +import rule570 from './persistence_microsoft_365_exchange_management_role_assignment.json'; +import rule571 from './persistence_microsoft_365_global_administrator_role_assign.json'; +import rule572 from './persistence_microsoft_365_teams_custom_app_interaction_allowed.json'; +import rule573 from './persistence_microsoft_365_teams_external_access_enabled.json'; +import rule574 from './persistence_microsoft_365_teams_guest_access_enabled.json'; +import rule575 from './persistence_ml_linux_anomalous_process_all_hosts.json'; +import rule576 from './persistence_ml_rare_process_by_host_linux.json'; +import rule577 from './persistence_ml_rare_process_by_host_windows.json'; +import rule578 from './persistence_ml_windows_anomalous_path_activity.json'; +import rule579 from './persistence_ml_windows_anomalous_process_all_hosts.json'; +import rule580 from './persistence_ml_windows_anomalous_process_creation.json'; +import rule581 from './persistence_ml_windows_anomalous_service.json'; +import rule582 from './persistence_modification_sublime_app_plugin_or_script.json'; +import rule583 from './persistence_ms_office_addins_file.json'; +import rule584 from './persistence_ms_outlook_vba_template.json'; +import rule585 from './persistence_msds_alloweddelegateto_krbtgt.json'; +import rule586 from './persistence_okta_attempt_to_modify_or_delete_application_sign_on_policy.json'; +import rule587 from './persistence_periodic_tasks_file_mdofiy.json'; +import rule588 from './persistence_powershell_exch_mailbox_activesync_add_device.json'; +import rule589 from './persistence_priv_escalation_via_accessibility_features.json'; +import rule590 from './persistence_rds_cluster_creation.json'; +import rule591 from './persistence_rds_group_creation.json'; +import rule592 from './persistence_rds_instance_creation.json'; +import rule593 from './persistence_redshift_instance_creation.json'; +import rule594 from './persistence_registry_uncommon.json'; +import rule595 from './persistence_remote_password_reset.json'; +import rule596 from './persistence_route_53_domain_transfer_lock_disabled.json'; +import rule597 from './persistence_route_53_domain_transferred_to_another_account.json'; +import rule598 from './persistence_route_53_hosted_zone_associated_with_a_vpc.json'; +import rule599 from './persistence_route_table_created.json'; +import rule600 from './persistence_route_table_modified_or_deleted.json'; +import rule601 from './persistence_run_key_and_startup_broad.json'; +import rule602 from './persistence_runtime_run_key_startup_susp_procs.json'; +import rule603 from './persistence_scheduled_task_creation_winlog.json'; +import rule604 from './persistence_scheduled_task_updated.json'; +import rule605 from './persistence_screensaver_engine_unexpected_child_process.json'; +import rule606 from './persistence_screensaver_plist_file_modification.json'; +import rule607 from './persistence_sdprop_exclusion_dsheuristics.json'; +import rule608 from './persistence_services_registry.json'; +import rule609 from './persistence_shell_activity_by_web_server.json'; +import rule610 from './persistence_shell_profile_modification.json'; +import rule611 from './persistence_ssh_authorized_keys_modification.json'; +import rule612 from './persistence_startup_folder_file_written_by_suspicious_process.json'; +import rule613 from './persistence_startup_folder_file_written_by_unsigned_process.json'; +import rule614 from './persistence_startup_folder_scripts.json'; +import rule615 from './persistence_suspicious_calendar_modification.json'; +import rule616 from './persistence_suspicious_com_hijack_registry.json'; +import rule617 from './persistence_suspicious_image_load_scheduled_task_ms_office.json'; +import rule618 from './persistence_suspicious_scheduled_task_runtime.json'; +import rule619 from './persistence_suspicious_service_created_registry.json'; +import rule620 from './persistence_system_shells_via_services.json'; +import rule621 from './persistence_time_provider_mod.json'; +import rule622 from './persistence_user_account_added_to_privileged_group_ad.json'; +import rule623 from './persistence_user_account_creation.json'; +import rule624 from './persistence_user_added_as_owner_for_azure_application.json'; +import rule625 from './persistence_user_added_as_owner_for_azure_service_principal.json'; +import rule626 from './persistence_via_application_shimming.json'; +import rule627 from './persistence_via_atom_init_file_modification.json'; +import rule628 from './persistence_via_bits_job_notify_command.json'; +import rule629 from './persistence_via_hidden_run_key_valuename.json'; +import rule630 from './persistence_via_lsa_security_support_provider_registry.json'; +import rule631 from './persistence_via_telemetrycontroller_scheduledtask_hijack.json'; +import rule632 from './persistence_via_update_orchestrator_service_hijack.json'; +import rule633 from './persistence_via_windows_management_instrumentation_event_subscription.json'; +import rule634 from './persistence_via_wmi_stdregprov_run_services.json'; +import rule635 from './persistence_webshell_detection.json'; +import rule636 from './privilege_escalation_applescript_with_admin_privs.json'; +import rule637 from './privilege_escalation_aws_suspicious_saml_activity.json'; +import rule638 from './privilege_escalation_azure_kubernetes_rolebinding_created.json'; +import rule639 from './privilege_escalation_create_process_as_different_user.json'; +import rule640 from './privilege_escalation_cyberarkpas_error_audit_event_promotion.json'; +import rule641 from './privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json'; +import rule642 from './privilege_escalation_disable_uac_registry.json'; +import rule643 from './privilege_escalation_echo_nopasswd_sudoers.json'; +import rule644 from './privilege_escalation_endgame_cred_manipulation_detected.json'; +import rule645 from './privilege_escalation_endgame_cred_manipulation_prevented.json'; +import rule646 from './privilege_escalation_endgame_permission_theft_detected.json'; +import rule647 from './privilege_escalation_endgame_permission_theft_prevented.json'; +import rule648 from './privilege_escalation_endgame_process_injection_detected.json'; +import rule649 from './privilege_escalation_endgame_process_injection_prevented.json'; +import rule650 from './privilege_escalation_explicit_creds_via_scripting.json'; +import rule651 from './privilege_escalation_exploit_adobe_acrobat_updater.json'; +import rule652 from './privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json'; +import rule653 from './privilege_escalation_group_policy_iniscript.json'; +import rule654 from './privilege_escalation_group_policy_privileged_groups.json'; +import rule655 from './privilege_escalation_group_policy_scheduled_task.json'; +import rule656 from './privilege_escalation_installertakeover.json'; +import rule657 from './privilege_escalation_krbrelayup_service_creation.json'; +import rule658 from './privilege_escalation_ld_preload_shared_object_modif.json'; +import rule659 from './privilege_escalation_local_user_added_to_admin.json'; +import rule660 from './privilege_escalation_lsa_auth_package.json'; +import rule661 from './privilege_escalation_ml_linux_anomalous_sudo_activity.json'; +import rule662 from './privilege_escalation_ml_windows_rare_user_runas_event.json'; +import rule663 from './privilege_escalation_named_pipe_impersonation.json'; +import rule664 from './privilege_escalation_new_or_modified_federation_domain.json'; +import rule665 from './privilege_escalation_persistence_phantom_dll.json'; +import rule666 from './privilege_escalation_pkexec_envar_hijack.json'; +import rule667 from './privilege_escalation_pod_created_with_hostipc.json'; +import rule668 from './privilege_escalation_pod_created_with_hostnetwork.json'; +import rule669 from './privilege_escalation_pod_created_with_hostpid.json'; +import rule670 from './privilege_escalation_pod_created_with_sensitive_hospath_volume.json'; +import rule671 from './privilege_escalation_port_monitor_print_pocessor_abuse.json'; +import rule672 from './privilege_escalation_posh_token_impersonation.json'; +import rule673 from './privilege_escalation_printspooler_registry_copyfiles.json'; +import rule674 from './privilege_escalation_printspooler_service_suspicious_file.json'; +import rule675 from './privilege_escalation_printspooler_suspicious_file_deletion.json'; +import rule676 from './privilege_escalation_printspooler_suspicious_spl_file.json'; +import rule677 from './privilege_escalation_privileged_pod_created.json'; +import rule678 from './privilege_escalation_rogue_windir_environment_var.json'; +import rule679 from './privilege_escalation_root_crontab_filemod.json'; +import rule680 from './privilege_escalation_root_login_without_mfa.json'; +import rule681 from './privilege_escalation_samaccountname_spoofing_attack.json'; +import rule682 from './privilege_escalation_setuid_setgid_bit_set_via_chmod.json'; +import rule683 from './privilege_escalation_shadow_file_read.json'; +import rule684 from './privilege_escalation_sts_assumerole_usage.json'; +import rule685 from './privilege_escalation_sts_getsessiontoken_abuse.json'; +import rule686 from './privilege_escalation_sudo_buffer_overflow.json'; +import rule687 from './privilege_escalation_sudoers_file_mod.json'; +import rule688 from './privilege_escalation_suspicious_assignment_of_controller_service_account.json'; +import rule689 from './privilege_escalation_suspicious_dnshostname_update.json'; +import rule690 from './privilege_escalation_uac_bypass_com_clipup.json'; +import rule691 from './privilege_escalation_uac_bypass_com_ieinstal.json'; +import rule692 from './privilege_escalation_uac_bypass_com_interface_icmluautil.json'; +import rule693 from './privilege_escalation_uac_bypass_diskcleanup_hijack.json'; +import rule694 from './privilege_escalation_uac_bypass_dll_sideloading.json'; +import rule695 from './privilege_escalation_uac_bypass_event_viewer.json'; +import rule696 from './privilege_escalation_uac_bypass_mock_windir.json'; +import rule697 from './privilege_escalation_uac_bypass_winfw_mmc_hijack.json'; +import rule698 from './privilege_escalation_unshare_namesapce_manipulation.json'; +import rule699 from './privilege_escalation_unusual_parentchild_relationship.json'; +import rule700 from './privilege_escalation_unusual_printspooler_childprocess.json'; +import rule701 from './privilege_escalation_unusual_svchost_childproc_childless.json'; +import rule702 from './privilege_escalation_updateassumerolepolicy.json'; +import rule703 from './privilege_escalation_via_rogue_named_pipe.json'; +import rule704 from './privilege_escalation_windows_service_via_unusual_client.json'; +import rule705 from './resource_development_ml_linux_anomalous_compiler_activity.json'; +import rule706 from './threat_intel_filebeat8x.json'; +import rule707 from './threat_intel_fleet_integrations.json'; +export const rawRules = [ + rule1, + rule2, + rule3, + rule4, + rule5, + rule6, + rule7, + rule8, + rule9, + rule10, + rule11, + rule12, + rule13, + rule14, + rule15, + rule16, + rule17, + rule18, + rule19, + rule20, + rule21, + rule22, + rule23, + rule24, + rule25, + rule26, + rule27, + rule28, + rule29, + rule30, + rule31, + rule32, + rule33, + rule34, + rule35, + rule36, + rule37, + rule38, + rule39, + rule40, + rule41, + rule42, + rule43, + rule44, + rule45, + rule46, + rule47, + rule48, + rule49, + rule50, + rule51, + rule52, + rule53, + rule54, + rule55, + rule56, + rule57, + rule58, + rule59, + rule60, + rule61, + rule62, + rule63, + rule64, + rule65, + rule66, + rule67, + rule68, + rule69, + rule70, + rule71, + rule72, + rule73, + rule74, + rule75, + rule76, + rule77, + rule78, + rule79, + rule80, + rule81, + rule82, + rule83, + rule84, + rule85, + rule86, + rule87, + rule88, + rule89, + rule90, + rule91, + rule92, + rule93, + rule94, + rule95, + rule96, + rule97, + rule98, + rule99, + rule100, + rule101, + rule102, + rule103, + rule104, + rule105, + rule106, + rule107, + rule108, + rule109, + rule110, + rule111, + rule112, + rule113, + rule114, + rule115, + rule116, + rule117, + rule118, + rule119, + rule120, + rule121, + rule122, + rule123, + rule124, + rule125, + rule126, + rule127, + rule128, + rule129, + rule130, + rule131, + rule132, + rule133, + rule134, + rule135, + rule136, + rule137, + rule138, + rule139, + rule140, + rule141, + rule142, + rule143, + rule144, + rule145, + rule146, + rule147, + rule148, + rule149, + rule150, + rule151, + rule152, + rule153, + rule154, + rule155, + rule156, + rule157, + rule158, + rule159, + rule160, + rule161, + rule162, + rule163, + rule164, + rule165, + rule166, + rule167, + rule168, + rule169, + rule170, + rule171, + rule172, + rule173, + rule174, + rule175, + rule176, + rule177, + rule178, + rule179, + rule180, + rule181, + rule182, + rule183, + rule184, + rule185, + rule186, + rule187, + rule188, + rule189, + rule190, + rule191, + rule192, + rule193, + rule194, + rule195, + rule196, + rule197, + rule198, + rule199, + rule200, + rule201, + rule202, + rule203, + rule204, + rule205, + rule206, + rule207, + rule208, + rule209, + rule210, + rule211, + rule212, + rule213, + rule214, + rule215, + rule216, + rule217, + rule218, + rule219, + rule220, + rule221, + rule222, + rule223, + rule224, + rule225, + rule226, + rule227, + rule228, + rule229, + rule230, + rule231, + rule232, + rule233, + rule234, + rule235, + rule236, + rule237, + rule238, + rule239, + rule240, + rule241, + rule242, + rule243, + rule244, + rule245, + rule246, + rule247, + rule248, + rule249, + rule250, + rule251, + rule252, + rule253, + rule254, + rule255, + rule256, + rule257, + rule258, + rule259, + rule260, + rule261, + rule262, + rule263, + rule264, + rule265, + rule266, + rule267, + rule268, + rule269, + rule270, + rule271, + rule272, + rule273, + rule274, + rule275, + rule276, + rule277, + rule278, + rule279, + rule280, + rule281, + rule282, + rule283, + rule284, + rule285, + rule286, + rule287, + rule288, + rule289, + rule290, + rule291, + rule292, + rule293, + rule294, + rule295, + rule296, + rule297, + rule298, + rule299, + rule300, + rule301, + rule302, + rule303, + rule304, + rule305, + rule306, + rule307, + rule308, + rule309, + rule310, + rule311, + rule312, + rule313, + rule314, + rule315, + rule316, + rule317, + rule318, + rule319, + rule320, + rule321, + rule322, + rule323, + rule324, + rule325, + rule326, + rule327, + rule328, + rule329, + rule330, + rule331, + rule332, + rule333, + rule334, + rule335, + rule336, + rule337, + rule338, + rule339, + rule340, + rule341, + rule342, + rule343, + rule344, + rule345, + rule346, + rule347, + rule348, + rule349, + rule350, + rule351, + rule352, + rule353, + rule354, + rule355, + rule356, + rule357, + rule358, + rule359, + rule360, + rule361, + rule362, + rule363, + rule364, + rule365, + rule366, + rule367, + rule368, + rule369, + rule370, + rule371, + rule372, + rule373, + rule374, + rule375, + rule376, + rule377, + rule378, + rule379, + rule380, + rule381, + rule382, + rule383, + rule384, + rule385, + rule386, + rule387, + rule388, + rule389, + rule390, + rule391, + rule392, + rule393, + rule394, + rule395, + rule396, + rule397, + rule398, + rule399, + rule400, + rule401, + rule402, + rule403, + rule404, + rule405, + rule406, + rule407, + rule408, + rule409, + rule410, + rule411, + rule412, + rule413, + rule414, + rule415, + rule416, + rule417, + rule418, + rule419, + rule420, + rule421, + rule422, + rule423, + rule424, + rule425, + rule426, + rule427, + rule428, + rule429, + rule430, + rule431, + rule432, + rule433, + rule434, + rule435, + rule436, + rule437, + rule438, + rule439, + rule440, + rule441, + rule442, + rule443, + rule444, + rule445, + rule446, + rule447, + rule448, + rule449, + rule450, + rule451, + rule452, + rule453, + rule454, + rule455, + rule456, + rule457, + rule458, + rule459, + rule460, + rule461, + rule462, + rule463, + rule464, + rule465, + rule466, + rule467, + rule468, + rule469, + rule470, + rule471, + rule472, + rule473, + rule474, + rule475, + rule476, + rule477, + rule478, + rule479, + rule480, + rule481, + rule482, + rule483, + rule484, + rule485, + rule486, + rule487, + rule488, + rule489, + rule490, + rule491, + rule492, + rule493, + rule494, + rule495, + rule496, + rule497, + rule498, + rule499, + rule500, + rule501, + rule502, + rule503, + rule504, + rule505, + rule506, + rule507, + rule508, + rule509, + rule510, + rule511, + rule512, + rule513, + rule514, + rule515, + rule516, + rule517, + rule518, + rule519, + rule520, + rule521, + rule522, + rule523, + rule524, + rule525, + rule526, + rule527, + rule528, + rule529, + rule530, + rule531, + rule532, + rule533, + rule534, + rule535, + rule536, + rule537, + rule538, + rule539, + rule540, + rule541, + rule542, + rule543, + rule544, + rule545, + rule546, + rule547, + rule548, + rule549, + rule550, + rule551, + rule552, + rule553, + rule554, + rule555, + rule556, + rule557, + rule558, + rule559, + rule560, + rule561, + rule562, + rule563, + rule564, + rule565, + rule566, + rule567, + rule568, + rule569, + rule570, + rule571, + rule572, + rule573, + rule574, + rule575, + rule576, + rule577, + rule578, + rule579, + rule580, + rule581, + rule582, + rule583, + rule584, + rule585, + rule586, + rule587, + rule588, + rule589, + rule590, + rule591, + rule592, + rule593, + rule594, + rule595, + rule596, + rule597, + rule598, + rule599, + rule600, + rule601, + rule602, + rule603, + rule604, + rule605, + rule606, + rule607, + rule608, + rule609, + rule610, + rule611, + rule612, + rule613, + rule614, + rule615, + rule616, + rule617, + rule618, + rule619, + rule620, + rule621, + rule622, + rule623, + rule624, + rule625, + rule626, + rule627, + rule628, + rule629, + rule630, + rule631, + rule632, + rule633, + rule634, + rule635, + rule636, + rule637, + rule638, + rule639, + rule640, + rule641, + rule642, + rule643, + rule644, + rule645, + rule646, + rule647, + rule648, + rule649, + rule650, + rule651, + rule652, + rule653, + rule654, + rule655, + rule656, + rule657, + rule658, + rule659, + rule660, + rule661, + rule662, + rule663, + rule664, + rule665, + rule666, + rule667, + rule668, + rule669, + rule670, + rule671, + rule672, + rule673, + rule674, + rule675, + rule676, + rule677, + rule678, + rule679, + rule680, + rule681, + rule682, + rule683, + rule684, + rule685, + rule686, + rule687, + rule688, + rule689, + rule690, + rule691, + rule692, + rule693, + rule694, + rule695, + rule696, + rule697, + rule698, + rule699, + rule700, + rule701, + rule702, + rule703, + rule704, + rule705, + rule706, + rule707, +]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_anonymous_request_authorized.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_anonymous_request_authorized.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_anonymous_request_authorized.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_anonymous_request_authorized.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_azure_active_directory_powershell_signin.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_consent_grant_attack_via_azure_registered_application.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_console_login_root.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_console_login_root.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_console_login_root.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_console_login_root.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_evasion_suspicious_htm_file_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_evasion_suspicious_htm_file_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_evasion_suspicious_htm_file_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_evasion_suspicious_htm_file_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_external_guest_user_invite.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_external_guest_user_invite.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_external_guest_user_invite.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_external_guest_user_invite.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_gcp_iam_custom_role_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_microsoft_365_exchange_anti_phish_rule_mod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_microsoft_365_exchange_safelinks_disabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_microsoft_365_user_restricted_from_sending_email.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_auth_rare_hour_for_a_user_to_logon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_auth_rare_hour_for_a_user_to_logon.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_auth_rare_hour_for_a_user_to_logon.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_auth_rare_hour_for_a_user_to_logon.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_auth_rare_source_ip_for_a_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_auth_rare_source_ip_for_a_user.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_auth_rare_source_ip_for_a_user.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_auth_rare_source_ip_for_a_user.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_auth_rare_user_logon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_auth_rare_user_logon.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_auth_rare_user_logon.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_auth_rare_user_logon.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_linux_anomalous_user_name.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_linux_anomalous_user_name.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_linux_anomalous_user_name.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_linux_anomalous_user_name.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_windows_anomalous_user_name.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_windows_anomalous_user_name.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_windows_anomalous_user_name.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_windows_anomalous_user_name.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_windows_rare_user_type10_remote_login.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_windows_rare_user_type10_remote_login.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_ml_windows_rare_user_type10_remote_login.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_ml_windows_rare_user_type10_remote_login.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_o365_user_reported_phish_malware.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_o365_user_reported_phish_malware.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_o365_user_reported_phish_malware.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_o365_user_reported_phish_malware.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_okta_user_attempted_unauthorized_access.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_password_recovery.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_password_recovery.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_password_recovery.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_password_recovery.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_rpc_remote_procedure_call_from_the_internet.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_rpc_remote_procedure_call_to_the_internet.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_script_executing_powershell.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_script_executing_powershell.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_script_executing_powershell.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_scripts_process_started_via_wmi.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_smb_windows_file_sharing_activity_to_the_internet.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_activity_reported_by_okta_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_activity_reported_by_okta_user.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_activity_reported_by_okta_user.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_activity_reported_by_okta_user.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_mac_ms_office_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_mac_ms_office_child_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_mac_ms_office_child_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_mac_ms_office_child_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_exchange_files.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_ms_exchange_files.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_exchange_files.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_ms_exchange_files.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_exchange_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_ms_exchange_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_exchange_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_ms_exchange_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_exchange_worker_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_ms_exchange_worker_child_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_exchange_worker_child_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_ms_exchange_worker_child_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_ms_office_child_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_suspicious_ms_outlook_child_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unsecure_elasticsearch_node.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_unsecure_elasticsearch_node.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unsecure_elasticsearch_node.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_unsecure_elasticsearch_node.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_unusual_dns_service_children.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_children.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_unusual_dns_service_children.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_unusual_dns_service_file_writes.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_via_explorer_suspicious_child_parent_args.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_system_manager.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_via_system_manager.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_via_system_manager.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_via_system_manager.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_zoom_meeting_with_no_passcode.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_zoom_meeting_with_no_passcode.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/initial_access_zoom_meeting_with_no_passcode.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/initial_access_zoom_meeting_with_no_passcode.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_cmd_service.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_cmd_service.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_cmd_service.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_cmd_service.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_credential_access_kerberos_bifrostconsole.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_dcom_hta.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_hta.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_dcom_hta.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_dcom_mmc20.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_mmc20.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_dcom_mmc20.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_dcom_shellwindow_shellbrowserwindow.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_direct_outbound_smb_connection.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_dns_server_overflow.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_dns_server_overflow.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_dns_server_overflow.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_evasion_rdp_shadowing.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_evasion_rdp_shadowing.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_evasion_rdp_shadowing.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_evasion_rdp_shadowing.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_executable_tool_transfer_smb.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_execution_from_tsclient_mup.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_execution_via_file_shares_sequence.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_incoming_winrm_shell_execution.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_incoming_wmi.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_incoming_wmi.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_incoming_wmi.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_malware_uploaded_onedrive.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_malware_uploaded_onedrive.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_malware_uploaded_onedrive.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_malware_uploaded_onedrive.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_malware_uploaded_sharepoint.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_malware_uploaded_sharepoint.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_malware_uploaded_sharepoint.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_malware_uploaded_sharepoint.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_mount_hidden_or_webdav_share_net.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mounting_smb_share.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_mounting_smb_share.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_mounting_smb_share.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_mounting_smb_share.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_powershell_remoting_target.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_powershell_remoting_target.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_powershell_remoting_target.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_rdp_enabled_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_enabled_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_rdp_enabled_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_rdp_sharprdp_target.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_remote_file_copy_hidden_share.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_remote_services.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_services.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_remote_services.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_remote_ssh_login_enabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_task_creation_winlog.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_remote_task_creation_winlog.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_remote_task_creation_winlog.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_remote_task_creation_winlog.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_scheduled_task_target.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_scheduled_task_target.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_scheduled_task_target.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_service_control_spawned_script_int.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_service_control_spawned_script_int.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_service_control_spawned_script_int.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_service_control_spawned_script_int.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_suspicious_rdp_client_imageload.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_external.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_telnet_network_activity_external.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_external.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_telnet_network_activity_external.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_internal.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_telnet_network_activity_internal.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_telnet_network_activity_internal.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_telnet_network_activity_internal.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_via_startup_folder_rdp_smb.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_vpn_connection_attempt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_vpn_connection_attempt.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/lateral_movement_vpn_connection_attempt.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/lateral_movement_vpn_connection_attempt.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_cloudtrail_error_message_spike.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_error_message_spike.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_cloudtrail_error_message_spike.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_error_code.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_cloudtrail_rare_error_code.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_error_code.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_cloudtrail_rare_error_code.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_cloudtrail_rare_method_by_city.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_cloudtrail_rare_method_by_country.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_cloudtrail_rare_method_by_user.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_denies.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_high_count_network_denies.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_denies.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_high_count_network_denies.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_events.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_high_count_network_events.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_high_count_network_events.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_high_count_network_events.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_linux_anomalous_network_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_linux_anomalous_network_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_port_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_linux_anomalous_network_port_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_linux_anomalous_network_port_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_linux_anomalous_network_port_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_packetbeat_rare_server_domain.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_packetbeat_rare_server_domain.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_packetbeat_rare_server_domain.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_packetbeat_rare_server_domain.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_destination_country.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_rare_destination_country.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_rare_destination_country.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_rare_destination_country.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_spike_in_traffic_to_a_country.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_spike_in_traffic_to_a_country.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_spike_in_traffic_to_a_country.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_spike_in_traffic_to_a_country.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_network_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_windows_anomalous_network_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/ml_windows_anomalous_network_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/ml_windows_anomalous_network_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/notice.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/notice.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/notice.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/notice.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_threat_detected_by_okta_threatinsight.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/okta_threat_detected_by_okta_threatinsight.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/okta_threat_detected_by_okta_threatinsight.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/okta_threat_detected_by_okta_threatinsight.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_account_creation_hide_at_logon.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_account_creation_hide_at_logon.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_account_creation_hide_at_logon.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ad_adminsdholder.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ad_adminsdholder.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ad_adminsdholder.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ad_adminsdholder.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_privileges_assigned_to_okta_group.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_administrator_privileges_assigned_to_okta_group.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_privileges_assigned_to_okta_group.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_administrator_privileges_assigned_to_okta_group.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_role_assigned_to_okta_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_administrator_role_assigned_to_okta_user.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_administrator_role_assigned_to_okta_user.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_administrator_role_assigned_to_okta_user.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_adobe_hijack_persistence.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_adobe_hijack_persistence.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_adobe_hijack_persistence.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_app_compat_shim.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_app_compat_shim.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_app_compat_shim.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_app_compat_shim.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_appcertdlls_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appcertdlls_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_appcertdlls_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appinitdlls_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_appinitdlls_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_appinitdlls_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_appinitdlls_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_application_added_to_google_workspace_domain.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_application_added_to_google_workspace_domain.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_application_added_to_google_workspace_domain.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_application_added_to_google_workspace_domain.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_create_okta_api_token.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_attempt_to_create_okta_api_token.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_create_okta_api_token.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_attempt_to_create_okta_api_token.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_deactivate_mfa_for_okta_user_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_attempt_to_deactivate_mfa_for_okta_user_account.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_deactivate_mfa_for_okta_user_account.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_attempt_to_deactivate_mfa_for_okta_user_account.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_account_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_automation_account_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_account_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_automation_account_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_runbook_created_or_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_automation_runbook_created_or_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_runbook_created_or_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_automation_runbook_created_or_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_webhook_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_automation_webhook_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_automation_webhook_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_automation_webhook_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_conditional_access_policy_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_conditional_access_policy_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_conditional_access_policy_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_conditional_access_policy_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_global_administrator_role_assigned.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_global_administrator_role_assigned.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_global_administrator_role_assigned.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_global_administrator_role_assigned.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_pim_user_added_global_admin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_pim_user_added_global_admin.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_pim_user_added_global_admin.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_pim_user_added_global_admin.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_azure_privileged_identity_management_role_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_chkconfig_service_add.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_chkconfig_service_add.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_chkconfig_service_add.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_chkconfig_service_add.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_change_launch_agents_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_creation_change_launch_agents_file.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_change_launch_agents_file.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_creation_change_launch_agents_file.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_hidden_login_item_osascript.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_creation_hidden_login_item_osascript.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_hidden_login_item_osascript.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_creation_hidden_login_item_osascript.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_modif_launch_deamon_sequence.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_creation_modif_launch_deamon_sequence.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_creation_modif_launch_deamon_sequence.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_creation_modif_launch_deamon_sequence.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_credential_access_authorization_plugin_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_credential_access_modify_auth_module_or_config.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_credential_access_modify_ssh_binaries.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_crontab_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_crontab_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_crontab_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_crontab_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_directory_services_plugins_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_directory_services_plugins_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_directory_services_plugins_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_docker_shortcuts_plist_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dontexpirepasswd_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_dontexpirepasswd_account.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dontexpirepasswd_account.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_dontexpirepasswd_account.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dynamic_linker_backup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_dynamic_linker_backup.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_dynamic_linker_backup.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_dynamic_linker_backup.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_network_acl_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ec2_network_acl_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_network_acl_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ec2_network_acl_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ec2_security_group_configuration_change_detection.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_emond_rules_file_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_file_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_emond_rules_file_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_process_execution.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_emond_rules_process_execution.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_emond_rules_process_execution.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_emond_rules_process_execution.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_enable_root_account.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_enable_root_account.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_enable_root_account.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_etc_file_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_etc_file_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_etc_file_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_etc_file_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_launch_agent_deamon_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_evasion_hidden_launch_agent_deamon_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_launch_agent_deamon_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_evasion_hidden_launch_agent_deamon_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_evasion_hidden_local_account_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_evasion_registry_ifeo_injection.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_evasion_registry_startup_shell_folder_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_exchange_suspicious_mailbox_right_delegation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exposed_service_created_with_type_nodeport.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_exposed_service_created_with_type_nodeport.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_exposed_service_created_with_type_nodeport.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_exposed_service_created_with_type_nodeport.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_finder_sync_plugin_pluginkit.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_finder_sync_plugin_pluginkit.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_finder_sync_plugin_pluginkit.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_finder_sync_plugin_pluginkit.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_folder_action_scripts_runtime.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_folder_action_scripts_runtime.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_folder_action_scripts_runtime.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_folder_action_scripts_runtime.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_gcp_iam_service_account_key_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_key_created_for_service_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_gcp_key_created_for_service_account.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_key_created_for_service_account.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_gcp_key_created_for_service_account.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_service_account_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_gcp_service_account_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gcp_service_account_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_gcp_service_account_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_2sv_policy_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_2sv_policy_disabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_2sv_policy_disabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_2sv_policy_disabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_admin_role_assigned_to_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_admin_role_assigned_to_user.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_admin_role_assigned_to_user.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_admin_role_assigned_to_user.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_custom_admin_role_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_policy_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_policy_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_policy_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_policy_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_role_modified.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_role_modified.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_role_modified.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_role_modified.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_user_group_access_modified_to_allow_external_access.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_user_group_access_modified_to_allow_external_access.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_user_group_access_modified_to_allow_external_access.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_user_group_access_modified_to_allow_external_access.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_user_organizational_unit_changed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_user_organizational_unit_changed.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_google_workspace_user_organizational_unit_changed.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_google_workspace_user_organizational_unit_changed.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_gpo_schtask_service_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_gpo_schtask_service_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_gpo_schtask_service_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_iam_group_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_iam_group_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_iam_group_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_iam_group_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_insmod_kernel_module_load.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_insmod_kernel_module_load.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_insmod_kernel_module_load.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_insmod_kernel_module_load.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kde_autostart_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_kde_autostart_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_kde_autostart_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_kde_autostart_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_job_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_local_scheduled_job_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_job_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_local_scheduled_job_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_local_scheduled_task_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_local_scheduled_task_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_local_scheduled_task_scripting.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_local_scheduled_task_scripting.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_local_scheduled_task_scripting.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_login_logout_hooks_defaults.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_login_logout_hooks_defaults.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_login_logout_hooks_defaults.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_login_logout_hooks_defaults.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_loginwindow_plist_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_loginwindow_plist_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_loginwindow_plist_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_mfa_disabled_for_azure_user.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_google_workspace_organization.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_mfa_disabled_for_google_workspace_organization.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_mfa_disabled_for_google_workspace_organization.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_mfa_disabled_for_google_workspace_organization.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_dkim_signing_config_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_exchange_dkim_signing_config_disabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_dkim_signing_config_disabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_exchange_dkim_signing_config_disabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_exchange_management_role_assignment.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_global_administrator_role_assign.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_global_administrator_role_assign.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_global_administrator_role_assign.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_global_administrator_role_assign.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_custom_app_interaction_allowed.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_teams_custom_app_interaction_allowed.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_custom_app_interaction_allowed.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_teams_custom_app_interaction_allowed.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_teams_external_access_enabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_microsoft_365_teams_guest_access_enabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_linux_anomalous_process_all_hosts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_linux_anomalous_process_all_hosts.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_linux_anomalous_process_all_hosts.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_linux_anomalous_process_all_hosts.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_rare_process_by_host_linux.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_rare_process_by_host_linux.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_rare_process_by_host_linux.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_rare_process_by_host_linux.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_rare_process_by_host_windows.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_rare_process_by_host_windows.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_rare_process_by_host_windows.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_rare_process_by_host_windows.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_windows_anomalous_path_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_windows_anomalous_path_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_windows_anomalous_path_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_windows_anomalous_path_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_windows_anomalous_process_all_hosts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_windows_anomalous_process_all_hosts.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_windows_anomalous_process_all_hosts.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_windows_anomalous_process_all_hosts.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_windows_anomalous_process_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_windows_anomalous_process_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_windows_anomalous_process_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_windows_anomalous_process_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_windows_anomalous_service.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_windows_anomalous_service.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ml_windows_anomalous_service.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ml_windows_anomalous_service.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_modification_sublime_app_plugin_or_script.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_modification_sublime_app_plugin_or_script.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_modification_sublime_app_plugin_or_script.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_modification_sublime_app_plugin_or_script.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_office_addins_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ms_office_addins_file.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_office_addins_file.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ms_office_addins_file.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_outlook_vba_template.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ms_outlook_vba_template.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ms_outlook_vba_template.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ms_outlook_vba_template.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_msds_alloweddelegateto_krbtgt.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_msds_alloweddelegateto_krbtgt.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_msds_alloweddelegateto_krbtgt.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_msds_alloweddelegateto_krbtgt.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_okta_attempt_to_modify_or_delete_application_sign_on_policy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_okta_attempt_to_modify_or_delete_application_sign_on_policy.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_okta_attempt_to_modify_or_delete_application_sign_on_policy.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_okta_attempt_to_modify_or_delete_application_sign_on_policy.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_periodic_tasks_file_mdofiy.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_powershell_exch_mailbox_activesync_add_device.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_powershell_exch_mailbox_activesync_add_device.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_powershell_exch_mailbox_activesync_add_device.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_powershell_exch_mailbox_activesync_add_device.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_priv_escalation_via_accessibility_features.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_cluster_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_rds_cluster_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_cluster_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_rds_cluster_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_group_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_rds_group_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_group_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_rds_group_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_rds_instance_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_rds_instance_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_rds_instance_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_redshift_instance_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_redshift_instance_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_redshift_instance_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_redshift_instance_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_registry_uncommon.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_registry_uncommon.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_registry_uncommon.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_remote_password_reset.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_remote_password_reset.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_remote_password_reset.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_remote_password_reset.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transfer_lock_disabled.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_route_53_domain_transfer_lock_disabled.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transfer_lock_disabled.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_route_53_domain_transfer_lock_disabled.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transferred_to_another_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_route_53_domain_transferred_to_another_account.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_domain_transferred_to_another_account.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_route_53_domain_transferred_to_another_account.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_hosted_zone_associated_with_a_vpc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_route_53_hosted_zone_associated_with_a_vpc.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_53_hosted_zone_associated_with_a_vpc.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_route_53_hosted_zone_associated_with_a_vpc.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_route_table_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_route_table_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_modified_or_deleted.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_route_table_modified_or_deleted.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_route_table_modified_or_deleted.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_route_table_modified_or_deleted.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_run_key_and_startup_broad.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_run_key_and_startup_broad.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_run_key_and_startup_broad.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_runtime_run_key_startup_susp_procs.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_scheduled_task_creation_winlog.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_scheduled_task_creation_winlog.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_scheduled_task_creation_winlog.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_scheduled_task_creation_winlog.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_scheduled_task_updated.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_scheduled_task_updated.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_scheduled_task_updated.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_scheduled_task_updated.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_screensaver_engine_unexpected_child_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_plist_file_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_screensaver_plist_file_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_screensaver_plist_file_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_screensaver_plist_file_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_sdprop_exclusion_dsheuristics.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_services_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_services_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_services_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_activity_by_web_server.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_shell_activity_by_web_server.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_activity_by_web_server.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_shell_activity_by_web_server.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_shell_profile_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_shell_profile_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_shell_profile_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ssh_authorized_keys_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_ssh_authorized_keys_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_ssh_authorized_keys_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_startup_folder_file_written_by_suspicious_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_startup_folder_file_written_by_unsigned_process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_startup_folder_scripts.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_startup_folder_scripts.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_startup_folder_scripts.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_suspicious_calendar_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_calendar_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_suspicious_calendar_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_suspicious_com_hijack_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_com_hijack_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_suspicious_com_hijack_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_suspicious_image_load_scheduled_task_ms_office.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_suspicious_scheduled_task_runtime.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_suspicious_service_created_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_suspicious_service_created_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_suspicious_service_created_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_system_shells_via_services.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_system_shells_via_services.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_system_shells_via_services.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_time_provider_mod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_time_provider_mod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_time_provider_mod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_user_account_added_to_privileged_group_ad.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_user_account_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_account_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_user_account_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_application.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_user_added_as_owner_for_azure_application.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_application.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_user_added_as_owner_for_azure_application.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_service_principal.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_user_added_as_owner_for_azure_service_principal.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_user_added_as_owner_for_azure_service_principal.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_user_added_as_owner_for_azure_service_principal.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_application_shimming.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_application_shimming.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_application_shimming.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_application_shimming.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_atom_init_file_modification.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_atom_init_file_modification.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_atom_init_file_modification.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_bits_job_notify_command.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_bits_job_notify_command.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_bits_job_notify_command.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_bits_job_notify_command.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_hidden_run_key_valuename.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_hidden_run_key_valuename.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_hidden_run_key_valuename.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_lsa_security_support_provider_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_telemetrycontroller_scheduledtask_hijack.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_update_orchestrator_service_hijack.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_windows_management_instrumentation_event_subscription.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_wmi_stdregprov_run_services.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_wmi_stdregprov_run_services.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_via_wmi_stdregprov_run_services.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_via_wmi_stdregprov_run_services.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_webshell_detection.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_webshell_detection.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/persistence_webshell_detection.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/persistence_webshell_detection.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_applescript_with_admin_privs.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_applescript_with_admin_privs.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_applescript_with_admin_privs.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_applescript_with_admin_privs.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_aws_suspicious_saml_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_aws_suspicious_saml_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_aws_suspicious_saml_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_aws_suspicious_saml_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_azure_kubernetes_rolebinding_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_azure_kubernetes_rolebinding_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_azure_kubernetes_rolebinding_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_azure_kubernetes_rolebinding_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_create_process_as_different_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_create_process_as_different_user.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_create_process_as_different_user.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_create_process_as_different_user.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_cyberarkpas_error_audit_event_promotion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_cyberarkpas_error_audit_event_promotion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_cyberarkpas_error_audit_event_promotion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_cyberarkpas_error_audit_event_promotion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_disable_uac_registry.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_disable_uac_registry.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_disable_uac_registry.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_echo_nopasswd_sudoers.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_cred_manipulation_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_cred_manipulation_detected.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_cred_manipulation_detected.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_cred_manipulation_detected.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_cred_manipulation_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_cred_manipulation_prevented.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_cred_manipulation_prevented.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_cred_manipulation_prevented.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_permission_theft_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_permission_theft_detected.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_permission_theft_detected.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_permission_theft_detected.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_permission_theft_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_permission_theft_prevented.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_permission_theft_prevented.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_permission_theft_prevented.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_process_injection_detected.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_process_injection_detected.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_process_injection_detected.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_process_injection_detected.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_process_injection_prevented.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_process_injection_prevented.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_endgame_process_injection_prevented.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_endgame_process_injection_prevented.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_explicit_creds_via_scripting.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_exploit_adobe_acrobat_updater.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_group_policy_iniscript.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_iniscript.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_group_policy_iniscript.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_group_policy_privileged_groups.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_group_policy_scheduled_task.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_installertakeover.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_installertakeover.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_installertakeover.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_krbrelayup_service_creation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_krbrelayup_service_creation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_krbrelayup_service_creation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_krbrelayup_service_creation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_ld_preload_shared_object_modif.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_local_user_added_to_admin.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_lsa_auth_package.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_lsa_auth_package.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_lsa_auth_package.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ml_linux_anomalous_sudo_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_ml_linux_anomalous_sudo_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ml_linux_anomalous_sudo_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_ml_linux_anomalous_sudo_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ml_windows_rare_user_runas_event.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_ml_windows_rare_user_runas_event.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_ml_windows_rare_user_runas_event.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_ml_windows_rare_user_runas_event.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_named_pipe_impersonation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_new_or_modified_federation_domain.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_persistence_phantom_dll.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pkexec_envar_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_pkexec_envar_hijack.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pkexec_envar_hijack.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_pkexec_envar_hijack.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pod_created_with_hostipc.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_pod_created_with_hostipc.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pod_created_with_hostipc.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_pod_created_with_hostipc.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pod_created_with_hostnetwork.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_pod_created_with_hostnetwork.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pod_created_with_hostnetwork.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_pod_created_with_hostnetwork.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pod_created_with_hostpid.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_pod_created_with_hostpid.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pod_created_with_hostpid.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_pod_created_with_hostpid.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pod_created_with_sensitive_hospath_volume.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_pod_created_with_sensitive_hospath_volume.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_pod_created_with_sensitive_hospath_volume.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_pod_created_with_sensitive_hospath_volume.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_port_monitor_print_pocessor_abuse.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_posh_token_impersonation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_posh_token_impersonation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_posh_token_impersonation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_posh_token_impersonation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_printspooler_registry_copyfiles.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_printspooler_service_suspicious_file.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_file_deletion.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_printspooler_suspicious_file_deletion.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_file_deletion.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_printspooler_suspicious_file_deletion.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_printspooler_suspicious_spl_file.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_privileged_pod_created.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_privileged_pod_created.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_privileged_pod_created.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_privileged_pod_created.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_rogue_windir_environment_var.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_root_crontab_filemod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_crontab_filemod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_root_crontab_filemod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_login_without_mfa.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_root_login_without_mfa.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_root_login_without_mfa.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_root_login_without_mfa.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_samaccountname_spoofing_attack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_samaccountname_spoofing_attack.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_samaccountname_spoofing_attack.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_samaccountname_spoofing_attack.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_setuid_setgid_bit_set_via_chmod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_shadow_file_read.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_shadow_file_read.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_shadow_file_read.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_shadow_file_read.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_assumerole_usage.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_sts_assumerole_usage.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_assumerole_usage.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_sts_assumerole_usage.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_getsessiontoken_abuse.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_sts_getsessiontoken_abuse.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sts_getsessiontoken_abuse.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_sts_getsessiontoken_abuse.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_sudo_buffer_overflow.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_sudoers_file_mod.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_sudoers_file_mod.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_sudoers_file_mod.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_suspicious_assignment_of_controller_service_account.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_suspicious_assignment_of_controller_service_account.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_suspicious_assignment_of_controller_service_account.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_suspicious_assignment_of_controller_service_account.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_suspicious_dnshostname_update.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_suspicious_dnshostname_update.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_suspicious_dnshostname_update.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_suspicious_dnshostname_update.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_com_clipup.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_com_ieinstal.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_com_interface_icmluautil.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_diskcleanup_hijack.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_dll_sideloading.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_event_viewer.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_mock_windir.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_uac_bypass_winfw_mmc_hijack.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unshare_namesapce_manipulation.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_unshare_namesapce_manipulation.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unshare_namesapce_manipulation.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_unshare_namesapce_manipulation.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_unusual_parentchild_relationship.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_unusual_printspooler_childprocess.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_unusual_svchost_childproc_childless.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_updateassumerolepolicy.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_via_rogue_named_pipe.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_via_rogue_named_pipe.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_via_rogue_named_pipe.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_via_rogue_named_pipe.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_windows_service_via_unusual_client.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_windows_service_via_unusual_client.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/privilege_escalation_windows_service_via_unusual_client.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/privilege_escalation_windows_service_via_unusual_client.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/resource_development_ml_linux_anomalous_compiler_activity.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/resource_development_ml_linux_anomalous_compiler_activity.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/resource_development_ml_linux_anomalous_compiler_activity.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/resource_development_ml_linux_anomalous_compiler_activity.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_filebeat8x.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/threat_intel_filebeat8x.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_filebeat8x.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/threat_intel_filebeat8x.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_fleet_integrations.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/threat_intel_fleet_integrations.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/threat_intel_fleet_integrations.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/threat_intel_fleet_integrations.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/README.md similarity index 94% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/README.md rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/README.md index 635839577fb2d..6b9638389e120 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/README.md +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/README.md @@ -6,7 +6,7 @@ 1. [Have the env params set up](https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/server/lib/detection_engine/README.md) -2. Create a new timelines template into `x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines` +2. Create a new timelines template into `x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines` ##### 2.a : Create a new template from UI and export it. @@ -14,7 +14,7 @@ 2. Go to timelines > templates > custom templates (a filter on the right) 3. Click `Create new timeline template` 4. Edit your template - 5. Export only **one** timeline template each time and put that in `x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines`. (For potential update requirement in the future, we put one timeline in each file to keep nice and clear) + 5. Export only **one** timeline template each time and put that in `x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines`. (For potential update requirement in the future, we put one timeline in each file to keep nice and clear) 6. Rename the file extension to `.json` 7. Check the chapter of `Fields to hightlight for on boarding a new prepackaged timeline` in this readme and update your template @@ -25,7 +25,7 @@ Please note that below template is just an example, please replace all your fields with whatever makes sense. Do check `Fields to hightlight for on boarding a new prepackaged timeline` to make sure the template can be created as expected. - cd x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines + cd x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines @@ -54,7 +54,7 @@ 4. ```sh ./timelines/regen_prepackage_timelines_index.sh``` -(this will update `x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson`) +(this will update `x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/index.ndjson`) @@ -83,7 +83,7 @@ sh ./timelines/find_timeline_by_filter.sh immutable template elastic ### How to update an existing prepackage timeline: -1. ```cd x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines``` +1. ```cd x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines``` 2. Open the json file you wish to update, and remember to bump the `templateTimelineVersion` diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/endpoint.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/endpoint.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/endpoint.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/endpoint.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/file_ex.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/file_ex.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/file_ex.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/file_ex.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/index.ndjson similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/index.ndjson diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/network.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/network.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network_ex.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/network_ex.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network_ex.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/network_ex.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/process.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/process.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process_ex.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/process_ex.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process_ex.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/process_ex.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/registry_ex.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/registry_ex.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/registry_ex.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/registry_ex.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/threat.json b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/threat.json similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/threat.json rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_timelines/threat.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/index.ts new file mode 100644 index 0000000000000..30a12c10bdb90 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/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 { createPrepackagedRules } from './api/install_prebuilt_rules_and_timelines/route'; +export * from './api/register_routes'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/create_prebuilt_rules.ts similarity index 56% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/create_prebuilt_rules.ts index b43c2180a07e8..54554e50a7dd7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/create_prebuilt_rules.ts @@ -6,17 +6,14 @@ */ import type { RulesClient } from '@kbn/alerting-plugin/server'; -import { MAX_RULES_TO_UPDATE_IN_PARALLEL } from '../../../../common/constants'; -import type { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; -import { initPromisePool } from '../../../utils/promise_pool'; -import { withSecuritySpan } from '../../../utils/with_security_span'; -import { createRules } from './create_rules'; +import { MAX_RULES_TO_UPDATE_IN_PARALLEL } from '../../../../../common/constants'; +import type { PrebuiltRuleToInstall } from '../../../../../common/detection_engine/prebuilt_rules'; +import { initPromisePool } from '../../../../utils/promise_pool'; +import { withSecuritySpan } from '../../../../utils/with_security_span'; +import { createRules } from '../../rule_management/logic/crud/create_rules'; -export const installPrepackagedRules = ( - rulesClient: RulesClient, - rules: AddPrepackagedRulesSchema[] -) => - withSecuritySpan('installPrepackagedRules', async () => { +export const createPrebuiltRules = (rulesClient: RulesClient, rules: PrebuiltRuleToInstall[]) => + withSecuritySpan('createPrebuiltRules', async () => { const result = await initPromisePool({ concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL, items: rules, @@ -31,6 +28,6 @@ export const installPrepackagedRules = ( }); if (result.errors.length > 0) { - throw new AggregateError(result.errors, 'Error installing prepackaged rules'); + throw new AggregateError(result.errors, 'Error installing new prebuilt rules'); } }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_latest_prebuilt_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_latest_prebuilt_rules.test.ts new file mode 100644 index 0000000000000..7c3f69990486b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_latest_prebuilt_rules.test.ts @@ -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 { isEmpty } from 'lodash/fp'; +import type { PrebuiltRuleToInstall } from '../../../../../common/detection_engine/prebuilt_rules'; +import { getFilesystemRules } from './get_latest_prebuilt_rules'; + +describe('Get latest prebuilt rules', () => { + describe('getFilesystemRules', () => { + test('should not throw any errors with the existing checked in pre-packaged rules', () => { + expect(() => getFilesystemRules()).not.toThrow(); + }); + + test('no rule should have the same rule_id as another rule_id', () => { + const prebuiltRules = getFilesystemRules(); + let existingRuleIds: PrebuiltRuleToInstall[] = []; + prebuiltRules.forEach((rule) => { + const foundDuplicate = existingRuleIds.reduce((accum, existingRule) => { + if (existingRule.rule_id === rule.rule_id) { + return `Found duplicate rule_id of ${rule.rule_id} between these two rule names of "${rule.name}" and "${existingRule.name}"`; + } else { + return accum; + } + }, ''); + if (!isEmpty(foundDuplicate)) { + expect(foundDuplicate).toEqual(''); + } else { + existingRuleIds = [...existingRuleIds, rule]; + } + }); + }); + + test('should throw an exception if a pre-packaged rule is not valid', () => { + // @ts-expect-error intentionally invalid argument + expect(() => getFilesystemRules([{ not_valid_made_up_key: true }])).toThrow(); + }); + + test('should throw an exception with a message having rule_id and name in it', () => { + expect(() => + // @ts-expect-error intentionally invalid argument + getFilesystemRules([{ name: 'rule name', rule_id: 'id-123' }]) + ).toThrow(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_latest_prebuilt_rules.ts similarity index 60% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_latest_prebuilt_rules.ts index f01f6820b2687..f1218f031fff8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_latest_prebuilt_rules.ts @@ -5,37 +5,75 @@ * 2.0. */ -import { BadRequestError } from '@kbn/securitysolution-es-utils'; -import { exactCheck, formatErrors } from '@kbn/securitysolution-io-ts-utils'; import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import type * as t from 'io-ts'; -import type { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; -import { addPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; -import type { ConfigType } from '../../../config'; -import { withSecuritySpan } from '../../../utils/with_security_span'; + +import { BadRequestError } from '@kbn/securitysolution-es-utils'; +import { exactCheck, formatErrors } from '@kbn/securitysolution-io-ts-utils'; + +import { PrebuiltRuleToInstall } from '../../../../../common/detection_engine/prebuilt_rules'; +import type { ConfigType } from '../../../../config'; +import { withSecuritySpan } from '../../../../utils/with_security_span'; + // TODO: convert rules files to TS and add explicit type definitions -import { rawRules } from './prepackaged_rules'; -import type { RuleAssetSavedObjectsClient } from './rule_asset/rule_asset_saved_objects_client'; -import type { IRuleAssetSOAttributes } from './types'; +import { rawRules } from '../content/prepackaged_rules'; +import type { + RuleAssetSavedObjectsClient, + IRuleAssetSOAttributes, +} from './rule_asset/rule_asset_saved_objects_client'; + +export const getLatestPrebuiltRules = async ( + client: RuleAssetSavedObjectsClient, + prebuiltRulesFromFileSystem: ConfigType['prebuiltRulesFromFileSystem'], + prebuiltRulesFromSavedObjects: ConfigType['prebuiltRulesFromSavedObjects'] +): Promise<Map<string, PrebuiltRuleToInstall>> => + withSecuritySpan('getLatestPrebuiltRules', async () => { + // build a map of the most recent version of each rule + const prebuilt = prebuiltRulesFromFileSystem ? getFilesystemRules() : []; + const ruleMap = new Map(prebuilt.map((r) => [r.rule_id, r])); + + // check the rules installed via fleet and create/update if the version is newer + if (prebuiltRulesFromSavedObjects) { + const fleetRules = await getFleetRules(client); + fleetRules.forEach((fleetRule) => { + const fsRule = ruleMap.get(fleetRule.rule_id); + + if (fsRule == null || fsRule.version < fleetRule.version) { + // add the new or updated rules to the map + ruleMap.set(fleetRule.rule_id, fleetRule); + } + }); + } + + return ruleMap; + }); + +/** + * Retrieve and validate prebuilt rules from "file system" (content/prepackaged_rules). + */ +export const getFilesystemRules = ( + // @ts-expect-error mock data is too loosely typed + rules: PrebuiltRuleToInstall[] = rawRules +): PrebuiltRuleToInstall[] => { + return validateFilesystemRules(rules); +}; /** * Validate the rules from the file system and throw any errors indicating to the developer * that they are adding incorrect schema rules. Also this will auto-flush in all the default * aspects such as default interval of 5 minutes, default arrays, etc... */ -export const validateAllPrepackagedRules = ( - rules: AddPrepackagedRulesSchema[] -): AddPrepackagedRulesSchema[] => { +const validateFilesystemRules = (rules: PrebuiltRuleToInstall[]): PrebuiltRuleToInstall[] => { return rules.map((rule) => { - const decoded = addPrepackagedRulesSchema.decode(rule); + const decoded = PrebuiltRuleToInstall.decode(rule); const checked = exactCheck(rule, decoded); - const onLeft = (errors: t.Errors): AddPrepackagedRulesSchema => { + const onLeft = (errors: t.Errors): PrebuiltRuleToInstall => { const ruleName = rule.name ? rule.name : '(rule name unknown)'; const ruleId = rule.rule_id ? rule.rule_id : '(rule rule_id unknown)'; throw new BadRequestError( - `name: "${ruleName}", rule_id: "${ruleId}" within the folder rules/prepackaged_rules ` + + `name: "${ruleName}", rule_id: "${ruleId}" within the folder content/prepackaged_rules ` + `is not a valid detection engine rule. Expect the system ` + `to not work with pre-packaged rules until this rule is fixed ` + `or the file is removed. Error is: ${formatErrors( @@ -44,24 +82,33 @@ export const validateAllPrepackagedRules = ( ); }; - const onRight = (schema: AddPrepackagedRulesSchema): AddPrepackagedRulesSchema => { - return schema as AddPrepackagedRulesSchema; + const onRight = (schema: PrebuiltRuleToInstall): PrebuiltRuleToInstall => { + return schema as PrebuiltRuleToInstall; }; return pipe(checked, fold(onLeft, onRight)); }); }; +/** + * Retrieve and validate prebuilt rules that were installed from Fleet as saved objects. + */ +const getFleetRules = async ( + client: RuleAssetSavedObjectsClient +): Promise<PrebuiltRuleToInstall[]> => { + const fleetResponse = await client.all(); + const fleetRules = fleetResponse.map((so) => so.attributes); + return validateFleetRules(fleetRules); +}; + /** * Validate the rules from Saved Objects created by Fleet. */ -export const validateAllRuleSavedObjects = ( - rules: IRuleAssetSOAttributes[] -): AddPrepackagedRulesSchema[] => { +const validateFleetRules = (rules: IRuleAssetSOAttributes[]): PrebuiltRuleToInstall[] => { return rules.map((rule) => { - const decoded = addPrepackagedRulesSchema.decode(rule); + const decoded = PrebuiltRuleToInstall.decode(rule); const checked = exactCheck(rule, decoded); - const onLeft = (errors: t.Errors): AddPrepackagedRulesSchema => { + const onLeft = (errors: t.Errors): PrebuiltRuleToInstall => { const ruleName = rule.name ? rule.name : '(rule name unknown)'; const ruleId = rule.rule_id ? rule.rule_id : '(rule rule_id unknown)'; throw new BadRequestError( @@ -74,53 +121,9 @@ export const validateAllRuleSavedObjects = ( ); }; - const onRight = (schema: AddPrepackagedRulesSchema): AddPrepackagedRulesSchema => { - return schema as AddPrepackagedRulesSchema; + const onRight = (schema: PrebuiltRuleToInstall): PrebuiltRuleToInstall => { + return schema as PrebuiltRuleToInstall; }; return pipe(checked, fold(onLeft, onRight)); }); }; - -/** - * Retrieve and validate rules that were installed from Fleet as saved objects. - */ -export const getFleetInstalledRules = async ( - client: RuleAssetSavedObjectsClient -): Promise<AddPrepackagedRulesSchema[]> => { - const fleetResponse = await client.all(); - const fleetRules = fleetResponse.map((so) => so.attributes); - return validateAllRuleSavedObjects(fleetRules); -}; - -export const getPrepackagedRules = ( - // @ts-expect-error mock data is too loosely typed - rules: AddPrepackagedRulesSchema[] = rawRules -): AddPrepackagedRulesSchema[] => { - return validateAllPrepackagedRules(rules); -}; - -export const getLatestPrepackagedRules = async ( - client: RuleAssetSavedObjectsClient, - prebuiltRulesFromFileSystem: ConfigType['prebuiltRulesFromFileSystem'], - prebuiltRulesFromSavedObjects: ConfigType['prebuiltRulesFromSavedObjects'] -): Promise<Map<string, AddPrepackagedRulesSchema>> => - withSecuritySpan('getLatestPrepackagedRules', async () => { - // build a map of the most recent version of each rule - const prepackaged = prebuiltRulesFromFileSystem ? getPrepackagedRules() : []; - const ruleMap = new Map(prepackaged.map((r) => [r.rule_id, r])); - - // check the rules installed via fleet and create/update if the version is newer - if (prebuiltRulesFromSavedObjects) { - const fleetRules = await getFleetInstalledRules(client); - fleetRules.forEach((fleetRule) => { - const fsRule = ruleMap.get(fleetRule.rule_id); - - if (fsRule == null || fsRule.version < fleetRule.version) { - // add the new or updated rules to the map - ruleMap.set(fleetRule.rule_id, fleetRule); - } - }); - } - - return ruleMap; - }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.test.ts similarity index 65% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.test.ts index 513d638afb2a7..b93ef2cc5178f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.test.ts @@ -6,73 +6,73 @@ */ import { getRulesToInstall } from './get_rules_to_install'; -import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getAddPrepackagedRulesSchemaMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; -import { prepackagedRulesToMap, rulesToMap } from './utils'; +import { getRuleMock } from '../../routes/__mocks__/request_responses'; +import { getPrebuiltRuleMock } from '../../../../../common/detection_engine/prebuilt_rules/mocks'; +import { getQueryRuleParams } from '../../rule_schema/mocks'; +import { prebuiltRulesToMap, rulesToMap } from './utils'; describe('get_rules_to_install', () => { test('should return empty array if both rule sets are empty', () => { - const update = getRulesToInstall(prepackagedRulesToMap([]), rulesToMap([])); + const update = getRulesToInstall(prebuiltRulesToMap([]), rulesToMap([])); expect(update).toEqual([]); }); test('should return empty array if the two rule ids match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem = getPrebuiltRuleMock(); ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getRuleMock(getQueryRuleParams()); installedRule.params.ruleId = 'rule-1'; const update = getRulesToInstall( - prepackagedRulesToMap([ruleFromFileSystem]), + prebuiltRulesToMap([ruleFromFileSystem]), rulesToMap([installedRule]) ); expect(update).toEqual([]); }); test('should return the rule to install if the id of the two rules do not match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem = getPrebuiltRuleMock(); ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getRuleMock(getQueryRuleParams()); installedRule.params.ruleId = 'rule-2'; const update = getRulesToInstall( - prepackagedRulesToMap([ruleFromFileSystem]), + prebuiltRulesToMap([ruleFromFileSystem]), rulesToMap([installedRule]) ); expect(update).toEqual([ruleFromFileSystem]); }); test('should return two rules to install if both the ids of the two rules do not match', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem2 = getPrebuiltRuleMock(); ruleFromFileSystem2.rule_id = 'rule-2'; const installedRule = getRuleMock(getQueryRuleParams()); installedRule.params.ruleId = 'rule-3'; const update = getRulesToInstall( - prepackagedRulesToMap([ruleFromFileSystem1, ruleFromFileSystem2]), + prebuiltRulesToMap([ruleFromFileSystem1, ruleFromFileSystem2]), rulesToMap([installedRule]) ); expect(update).toEqual([ruleFromFileSystem1, ruleFromFileSystem2]); }); test('should return two rules of three to install if both the ids of the two rules do not match but the third does', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem2 = getPrebuiltRuleMock(); ruleFromFileSystem2.rule_id = 'rule-2'; - const ruleFromFileSystem3 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem3 = getPrebuiltRuleMock(); ruleFromFileSystem3.rule_id = 'rule-3'; const installedRule = getRuleMock(getQueryRuleParams()); installedRule.params.ruleId = 'rule-3'; const update = getRulesToInstall( - prepackagedRulesToMap([ruleFromFileSystem1, ruleFromFileSystem2, ruleFromFileSystem3]), + prebuiltRulesToMap([ruleFromFileSystem1, ruleFromFileSystem2, ruleFromFileSystem3]), rulesToMap([installedRule]) ); expect(update).toEqual([ruleFromFileSystem1, ruleFromFileSystem2]); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.ts similarity index 56% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.ts index aa7a257a171e0..69d76cdc98e4a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_install.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; -import type { RuleAlertType } from './types'; +import type { PrebuiltRuleToInstall } from '../../../../../common/detection_engine/prebuilt_rules'; +import type { RuleAlertType } from '../../rule_schema'; export const getRulesToInstall = ( - latestPrePackagedRules: Map<string, AddPrepackagedRulesSchema>, + latestPrebuiltRules: Map<string, PrebuiltRuleToInstall>, installedRules: Map<string, RuleAlertType> ) => { - return Array.from(latestPrePackagedRules.values()).filter( + return Array.from(latestPrebuiltRules.values()).filter( (rule) => !installedRules.has(rule.rule_id) ); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.test.ts similarity index 85% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.test.ts index 7f48cca2078e1..bcf85d4095556 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.test.ts @@ -6,19 +6,19 @@ */ import { filterInstalledRules, getRulesToUpdate, mergeExceptionLists } from './get_rules_to_update'; -import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getAddPrepackagedRulesSchemaMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; -import { prepackagedRulesToMap, rulesToMap } from './utils'; +import { getRuleMock } from '../../routes/__mocks__/request_responses'; +import { getPrebuiltRuleMock } from '../../../../../common/detection_engine/prebuilt_rules/mocks'; +import { getQueryRuleParams } from '../../rule_schema/mocks'; +import { prebuiltRulesToMap, rulesToMap } from './utils'; describe('get_rules_to_update', () => { test('should return empty array if both rule sets are empty', () => { - const update = getRulesToUpdate(prepackagedRulesToMap([]), rulesToMap([])); + const update = getRulesToUpdate(prebuiltRulesToMap([]), rulesToMap([])); expect(update).toEqual([]); }); test('should return empty array if the rule_id of the two rules do not match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem = getPrebuiltRuleMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -26,14 +26,14 @@ describe('get_rules_to_update', () => { installedRule.params.ruleId = 'rule-2'; installedRule.params.version = 1; const update = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem]), + prebuiltRulesToMap([ruleFromFileSystem]), rulesToMap([installedRule]) ); expect(update).toEqual([]); }); test('should return empty array if the version of file system rule is less than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem = getPrebuiltRuleMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -41,14 +41,14 @@ describe('get_rules_to_update', () => { installedRule.params.ruleId = 'rule-1'; installedRule.params.version = 2; const update = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem]), + prebuiltRulesToMap([ruleFromFileSystem]), rulesToMap([installedRule]) ); expect(update).toEqual([]); }); test('should return empty array if the version of file system rule is the same as the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem = getPrebuiltRuleMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -56,14 +56,14 @@ describe('get_rules_to_update', () => { installedRule.params.ruleId = 'rule-1'; installedRule.params.version = 1; const update = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem]), + prebuiltRulesToMap([ruleFromFileSystem]), rulesToMap([installedRule]) ); expect(update).toEqual([]); }); test('should return the rule to update if the version of file system rule is greater than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem = getPrebuiltRuleMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -73,14 +73,14 @@ describe('get_rules_to_update', () => { installedRule.params.exceptionsList = []; const update = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem]), + prebuiltRulesToMap([ruleFromFileSystem]), rulesToMap([installedRule]) ); expect(update).toEqual([ruleFromFileSystem]); }); test('should return 1 rule out of 2 to update if the version of file system rule is greater than the installed version of just one', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem = getPrebuiltRuleMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -95,18 +95,18 @@ describe('get_rules_to_update', () => { installedRule2.params.exceptionsList = []; const update = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem]), + prebuiltRulesToMap([ruleFromFileSystem]), rulesToMap([installedRule1, installedRule2]) ); expect(update).toEqual([ruleFromFileSystem]); }); test('should return 2 rules out of 2 to update if the version of file system rule is greater than the installed version of both', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem2 = getPrebuiltRuleMock(); ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; @@ -121,14 +121,14 @@ describe('get_rules_to_update', () => { installedRule2.params.exceptionsList = []; const update = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem1, ruleFromFileSystem2]), + prebuiltRulesToMap([ruleFromFileSystem1, ruleFromFileSystem2]), rulesToMap([installedRule1, installedRule2]) ); expect(update).toEqual([ruleFromFileSystem1, ruleFromFileSystem2]); }); test('should add back an exception_list if it was removed by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -146,14 +146,14 @@ describe('get_rules_to_update', () => { installedRule1.params.exceptionsList = []; const [update] = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem1]), + prebuiltRulesToMap([ruleFromFileSystem1]), rulesToMap([installedRule1]) ); expect(update.exceptions_list).toEqual(ruleFromFileSystem1.exceptions_list); }); test('should not remove an additional exception_list if an additional one was added by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -178,7 +178,7 @@ describe('get_rules_to_update', () => { ]; const [update] = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem1]), + prebuiltRulesToMap([ruleFromFileSystem1]), rulesToMap([installedRule1]) ); expect(update.exceptions_list).toEqual([ @@ -188,7 +188,7 @@ describe('get_rules_to_update', () => { }); test('should not remove an existing exception_list if they are the same between the current installed one and the upgraded one', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -213,14 +213,14 @@ describe('get_rules_to_update', () => { ]; const [update] = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem1]), + prebuiltRulesToMap([ruleFromFileSystem1]), rulesToMap([installedRule1]) ); expect(update.exceptions_list).toEqual(ruleFromFileSystem1.exceptions_list); }); test('should not remove an existing exception_list if the rule has an empty exceptions list', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; @@ -238,19 +238,19 @@ describe('get_rules_to_update', () => { ]; const [update] = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem1]), + prebuiltRulesToMap([ruleFromFileSystem1]), rulesToMap([installedRule1]) ); expect(update.exceptions_list).toEqual(installedRule1.params.exceptionsList); }); test('should not remove an existing exception_list if the rule has an empty exceptions list for multiple rules', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem2 = getPrebuiltRuleMock(); ruleFromFileSystem2.exceptions_list = []; ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; @@ -279,7 +279,7 @@ describe('get_rules_to_update', () => { ]; const [update1, update2] = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem1, ruleFromFileSystem2]), + prebuiltRulesToMap([ruleFromFileSystem1, ruleFromFileSystem2]), rulesToMap([installedRule1, installedRule2]) ); expect(update1.exceptions_list).toEqual(installedRule1.params.exceptionsList); @@ -287,12 +287,12 @@ describe('get_rules_to_update', () => { }); test('should not remove an existing exception_list if the rule has an empty exceptions list for mixed rules', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem2 = getPrebuiltRuleMock(); ruleFromFileSystem2.exceptions_list = []; ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; @@ -330,7 +330,7 @@ describe('get_rules_to_update', () => { ]; const [update1, update2] = getRulesToUpdate( - prepackagedRulesToMap([ruleFromFileSystem1, ruleFromFileSystem2]), + prebuiltRulesToMap([ruleFromFileSystem1, ruleFromFileSystem2]), rulesToMap([installedRule1, installedRule2]) ); expect(update1.exceptions_list).toEqual(installedRule1.params.exceptionsList); @@ -343,7 +343,7 @@ describe('get_rules_to_update', () => { describe('filterInstalledRules', () => { test('should return "false" if the id of the two rules do not match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem = getPrebuiltRuleMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -355,7 +355,7 @@ describe('filterInstalledRules', () => { }); test('should return "false" if the version of file system rule is less than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem = getPrebuiltRuleMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -367,7 +367,7 @@ describe('filterInstalledRules', () => { }); test('should return "false" if the version of file system rule is the same as the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem = getPrebuiltRuleMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -379,7 +379,7 @@ describe('filterInstalledRules', () => { }); test('should return "true" to update if the version of file system rule is greater than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem = getPrebuiltRuleMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -395,7 +395,7 @@ describe('filterInstalledRules', () => { describe('mergeExceptionLists', () => { test('should add back an exception_list if it was removed by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -417,7 +417,7 @@ describe('mergeExceptionLists', () => { }); test('should not remove an additional exception_list if an additional one was added by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -449,7 +449,7 @@ describe('mergeExceptionLists', () => { }); test('should not remove an existing exception_list if they are the same between the current installed one and the upgraded one', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -478,7 +478,7 @@ describe('mergeExceptionLists', () => { }); test('should not remove an existing exception_list if the rule has an empty exceptions list', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); + const ruleFromFileSystem1 = getPrebuiltRuleMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.ts similarity index 60% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.ts index 8cfabf1889c7f..7547f16961cc1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/get_rules_to_update.ts @@ -5,21 +5,21 @@ * 2.0. */ -import type { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; -import type { RuleAlertType } from './types'; +import type { PrebuiltRuleToInstall } from '../../../../../common/detection_engine/prebuilt_rules'; +import type { RuleAlertType } from '../../rule_schema'; /** * Returns the rules to update by doing a compare to the rules from the file system against * the installed rules already. This also merges exception list items between the two since * exception list items can exist on both rules to update and already installed rules. - * @param latestPrePackagedRules The latest rules to check against installed + * @param latestPrebuiltRules The latest rules to check against installed * @param installedRules The installed rules */ export const getRulesToUpdate = ( - latestPrePackagedRules: Map<string, AddPrepackagedRulesSchema>, + latestPrebuiltRules: Map<string, PrebuiltRuleToInstall>, installedRules: Map<string, RuleAlertType> ) => { - return Array.from(latestPrePackagedRules.values()) + return Array.from(latestPrebuiltRules.values()) .filter((latestRule) => filterInstalledRules(latestRule, installedRules)) .map((latestRule) => mergeExceptionLists(latestRule, installedRules)); }; @@ -27,45 +27,44 @@ export const getRulesToUpdate = ( /** * Filters latest prepackaged rules that do not match the installed rules so you * only get back rules that are going to be updated - * @param latestPrePackagedRule The latest prepackaged rule version + * @param latestPrebuiltRule The latest prepackaged rule version * @param installedRules The installed rules to compare against for updates */ export const filterInstalledRules = ( - latestPrePackagedRule: AddPrepackagedRulesSchema, + latestPrebuiltRule: PrebuiltRuleToInstall, installedRules: Map<string, RuleAlertType> ): boolean => { - const installedRule = installedRules.get(latestPrePackagedRule.rule_id); + const installedRule = installedRules.get(latestPrebuiltRule.rule_id); - return !!installedRule && installedRule.params.version < latestPrePackagedRule.version; + return !!installedRule && installedRule.params.version < latestPrebuiltRule.version; }; /** * Given a rule from the file system and the set of installed rules this will merge the exception lists * from the installed rules onto the rules from the file system. - * @param latestPrePackagedRule The latest prepackaged rule version that might have exceptions_lists + * @param latestPrebuiltRule The latest prepackaged rule version that might have exceptions_lists * @param installedRules The installed rules which might have user driven exceptions_lists */ export const mergeExceptionLists = ( - latestPrePackagedRule: AddPrepackagedRulesSchema, + latestPrebuiltRule: PrebuiltRuleToInstall, installedRules: Map<string, RuleAlertType> -): AddPrepackagedRulesSchema => { - if (latestPrePackagedRule.exceptions_list != null) { - const installedRule = installedRules.get(latestPrePackagedRule.rule_id); +): PrebuiltRuleToInstall => { + if (latestPrebuiltRule.exceptions_list != null) { + const installedRule = installedRules.get(latestPrebuiltRule.rule_id); if (installedRule != null && installedRule.params.exceptionsList != null) { const installedExceptionList = installedRule.params.exceptionsList; - const fileSystemExceptions = latestPrePackagedRule.exceptions_list.filter( - (potentialDuplicate) => - installedExceptionList.every((item) => item.list_id !== potentialDuplicate.list_id) + const fileSystemExceptions = latestPrebuiltRule.exceptions_list.filter((potentialDuplicate) => + installedExceptionList.every((item) => item.list_id !== potentialDuplicate.list_id) ); return { - ...latestPrePackagedRule, + ...latestPrebuiltRule, exceptions_list: [...fileSystemExceptions, ...installedRule.params.exceptionsList], }; } else { - return latestPrePackagedRule; + return latestPrebuiltRule; } } else { - return latestPrePackagedRule; + return latestPrebuiltRule; } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/rule_asset/rule_asset_saved_object_mappings.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_asset/rule_asset_saved_object_mappings.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/rule_asset/rule_asset_saved_object_mappings.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_asset/rule_asset_saved_object_mappings.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/rule_asset/rule_asset_saved_objects_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_asset/rule_asset_saved_objects_client.ts similarity index 80% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/rule_asset/rule_asset_saved_objects_client.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_asset/rule_asset_saved_objects_client.ts index de1ea7987e457..be963fb3dae9e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/rule_asset/rule_asset_saved_objects_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/rule_asset/rule_asset_saved_objects_client.ts @@ -11,10 +11,22 @@ import type { SavedObjectsFindResponse, } from '@kbn/core/server'; import { ruleAssetSavedObjectType } from './rule_asset_saved_object_mappings'; -import type { IRuleAssetSavedObject } from '../types'; const DEFAULT_PAGE_SIZE = 100; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface IRuleAssetSOAttributes extends Record<string, any> { + rule_id: string | null | undefined; + version: string | null | undefined; + name: string | null | undefined; +} + +export interface IRuleAssetSavedObject { + type: string; + id: string; + attributes: IRuleAssetSOAttributes; +} + export interface RuleAssetSavedObjectsClient { find: ( options?: Omit<SavedObjectsFindOptions, 'type'> diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/update_prebuilt_rules.test.ts similarity index 73% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/update_prebuilt_rules.test.ts index 88d543cc77007..955d288068e33 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/update_prebuilt_rules.test.ts @@ -7,28 +7,30 @@ import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; import { savedObjectsClientMock } from '@kbn/core/server/mocks'; -import { getRuleMock, getFindResultWithSingleHit } from '../routes/__mocks__/request_responses'; -import { updatePrepackagedRules } from './update_prepacked_rules'; -import { patchRules } from './patch_rules'; +import { getRuleMock, getFindResultWithSingleHit } from '../../routes/__mocks__/request_responses'; +import { updatePrebuiltRules } from './update_prebuilt_rules'; +import { patchRules } from '../../rule_management/logic/crud/patch_rules'; import { - getAddPrepackagedRulesSchemaMock, - getAddPrepackagedThreatMatchRulesSchemaMock, -} from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; -import { ruleExecutionLogMock } from '../rule_monitoring/mocks'; -import { legacyMigrate } from './utils'; -import { getQueryRuleParams, getThreatRuleParams } from '../schemas/rule_schemas.mock'; + getPrebuiltRuleMock, + getPrebuiltThreatMatchRuleMock, +} from '../../../../../common/detection_engine/prebuilt_rules/mocks'; +import { ruleExecutionLogMock } from '../../rule_monitoring/mocks'; +import { legacyMigrate } from '../../rule_management'; +import { getQueryRuleParams, getThreatRuleParams } from '../../rule_schema/mocks'; -jest.mock('./patch_rules'); +jest.mock('../../rule_management/logic/crud/patch_rules'); -jest.mock('./utils', () => { - const actual = jest.requireActual('./utils'); +jest.mock('../../rule_management/logic/rule_actions/legacy_action_migration', () => { + const actual = jest.requireActual( + '../../rule_management/logic/rule_actions/legacy_action_migration' + ); return { ...actual, legacyMigrate: jest.fn(), }; }); -describe('updatePrepackagedRules', () => { +describe('updatePrebuiltRules', () => { let rulesClient: ReturnType<typeof rulesClientMock.create>; let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>; let ruleExecutionLog: ReturnType<typeof ruleExecutionLogMock.forRoutes.create>; @@ -50,10 +52,10 @@ describe('updatePrepackagedRules', () => { params: {}, }, ]; - const prepackagedRule = getAddPrepackagedRulesSchemaMock(); + const prepackagedRule = getPrebuiltRuleMock(); rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); - await updatePrepackagedRules( + await updatePrebuiltRules( rulesClient, savedObjectsClient, [{ ...prepackagedRule, actions }], @@ -83,14 +85,14 @@ describe('updatePrepackagedRules', () => { threat_indicator_path: 'test.path', threat_query: 'threat:*', }; - const prepackagedRule = getAddPrepackagedThreatMatchRulesSchemaMock(); + const prepackagedRule = getPrebuiltThreatMatchRuleMock(); rulesClient.find.mockResolvedValue({ ...getFindResultWithSingleHit(), data: [getRuleMock(getThreatRuleParams())], }); (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getThreatRuleParams())); - await updatePrepackagedRules( + await updatePrebuiltRules( rulesClient, savedObjectsClient, [{ ...prepackagedRule, ...updatedThreatParams }], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/update_prebuilt_rules.ts similarity index 73% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/update_prebuilt_rules.ts index aaee033dd8e96..341038097f448 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/update_prebuilt_rules.ts @@ -8,30 +8,34 @@ import { chunk } from 'lodash/fp'; import type { SavedObjectsClientContract } from '@kbn/core/server'; import type { RulesClient, PartialRule } from '@kbn/alerting-plugin/server'; -import type { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; -import { MAX_RULES_TO_UPDATE_IN_PARALLEL } from '../../../../common/constants'; -import { patchRules } from './patch_rules'; -import { readRules } from './read_rules'; -import type { RuleParams } from '../schemas/rule_schemas'; -import { legacyMigrate } from './utils'; -import { deleteRules } from './delete_rules'; -import { PrepackagedRulesError } from '../routes/rules/add_prepackaged_rules_route'; -import type { IRuleExecutionLogForRoutes } from '../rule_monitoring'; -import { createRules } from './create_rules'; -import { transformAlertToRuleAction } from '../../../../common/detection_engine/transform_actions'; + +import type { PrebuiltRuleToInstall } from '../../../../../common/detection_engine/prebuilt_rules'; +import { transformAlertToRuleAction } from '../../../../../common/detection_engine/transform_actions'; +import { MAX_RULES_TO_UPDATE_IN_PARALLEL } from '../../../../../common/constants'; + +import { legacyMigrate } from '../../rule_management'; +import { createRules } from '../../rule_management/logic/crud/create_rules'; +import { readRules } from '../../rule_management/logic/crud/read_rules'; +import { patchRules } from '../../rule_management/logic/crud/patch_rules'; +import { deleteRules } from '../../rule_management/logic/crud/delete_rules'; + +import type { IRuleExecutionLogForRoutes } from '../../rule_monitoring'; +import type { RuleParams } from '../../rule_schema'; + +import { PrepackagedRulesError } from '../api/install_prebuilt_rules_and_timelines/route'; /** - * Updates the prepackaged rules given a set of rules and output index. + * Updates existing prebuilt rules given a set of rules and output index. * This implements a chunked approach to not saturate network connections and * avoid being a "noisy neighbor". * @param rulesClient Alerting client * @param spaceId Current user spaceId * @param rules The rules to apply the update for */ -export const updatePrepackagedRules = async ( +export const updatePrebuiltRules = async ( rulesClient: RulesClient, savedObjectsClient: SavedObjectsClientContract, - rules: AddPrepackagedRulesSchema[], + rules: PrebuiltRuleToInstall[], ruleExecutionLog: IRuleExecutionLogForRoutes ): Promise<void> => { const ruleChunks = chunk(MAX_RULES_TO_UPDATE_IN_PARALLEL, rules); @@ -53,10 +57,10 @@ export const updatePrepackagedRules = async ( * @param rules The rules to apply the update for * @returns Promise of what was updated. */ -export const createPromises = ( +const createPromises = ( rulesClient: RulesClient, savedObjectsClient: SavedObjectsClientContract, - rules: AddPrepackagedRulesSchema[], + rules: PrebuiltRuleToInstall[], ruleExecutionLog: IRuleExecutionLogForRoutes ): Array<Promise<PartialRule<RuleParams> | null>> => { return rules.map(async (rule) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/utils.ts new file mode 100644 index 0000000000000..09f231f51d4ac --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/utils.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 type { PrebuiltRuleToInstall } from '../../../../../common/detection_engine/prebuilt_rules'; +import type { RuleAlertType } from '../../rule_schema'; + +/** + * Converts an array of prebuilt rules to a Map with rule IDs as keys + * + * @param rules Array of prebuilt rules + * @returns Map + */ +export const prebuiltRulesToMap = (rules: PrebuiltRuleToInstall[]) => + new Map(rules.map((rule) => [rule.rule_id, rule])); + +/** + * Converts an array of rules to a Map with rule IDs as keys + * + * @param rules Array of rules + * @returns Map + */ +export const rulesToMap = (rules: RuleAlertType[]) => + new Map(rules.map((rule) => [rule.params.ruleId, rule])); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 9f2f576313531..7b6c85d486638 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -6,17 +6,16 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { SavedObjectsFindResponse, SavedObjectsFindResult } from '@kbn/core/server'; import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; import { ruleTypeMappings } from '@kbn/securitysolution-rules'; - -import type { SavedObjectsFindResponse, SavedObjectsFindResult } from '@kbn/core/server'; +import type { SanitizedRule, ResolvedSanitizedRule } from '@kbn/alerting-plugin/common'; import { DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, DETECTION_ENGINE_PRIVILEGES_URL, DETECTION_ENGINE_QUERY_SIGNALS_URL, - DETECTION_ENGINE_PREPACKAGED_URL, DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL, DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL, DETECTION_ENGINE_RULES_BULK_ACTION, @@ -25,24 +24,31 @@ import { DETECTION_ENGINE_RULES_BULK_CREATE, DETECTION_ENGINE_RULES_URL_FIND, } from '../../../../../common/constants'; -import type { RuleAlertType, HapiReadableStream } from '../../rules/types'; -import { requestMock } from './request'; + +import { + PREBUILT_RULES_STATUS_URL, + PREBUILT_RULES_URL, +} from '../../../../../common/detection_engine/prebuilt_rules'; +import { + getPerformBulkActionSchemaMock, + getPerformBulkActionEditSchemaMock, +} from '../../../../../common/detection_engine/rule_management/mocks'; +import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/rule_schema/mocks'; import type { QuerySignalsSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/query_signals_index_schema'; import type { SetSignalsStatusSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/set_signal_status_schema'; -import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { getFinalizeSignalsMigrationSchemaMock } from '../../../../../common/detection_engine/schemas/request/finalize_signals_migration_schema.mock'; import { getSignalsMigrationStatusSchemaMock } from '../../../../../common/detection_engine/schemas/request/get_signals_migration_status_schema.mock'; -import type { RuleParams } from '../../schemas/rule_schemas'; -import type { SanitizedRule, ResolvedSanitizedRule } from '@kbn/alerting-plugin/common'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; -import { - getPerformBulkActionSchemaMock, - getPerformBulkActionEditSchemaMock, -} from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema.mock'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyRuleNotificationAlertType } from '../../notifications/legacy_types'; + // eslint-disable-next-line no-restricted-imports -import type { LegacyIRuleActionsAttributes } from '../../rule_actions/legacy_types'; +import type { + LegacyRuleNotificationAlertType, + LegacyIRuleActionsAttributes, +} from '../../rule_actions_legacy'; +import type { HapiReadableStream } from '../../rule_management/logic/import/hapi_readable_stream'; +import type { RuleAlertType, RuleParams } from '../../rule_schema'; +import { getQueryRuleParams } from '../../rule_schema/mocks'; + +import { requestMock } from './request'; export const typicalSetStatusSignalByIdsPayload = (): SetSignalsStatusSchemaDecoded => ({ signal_ids: ['somefakeid1', 'somefakeid2'], @@ -174,13 +180,13 @@ export const getPrivilegeRequest = (options: { auth?: { isAuthenticated: boolean export const addPrepackagedRulesRequest = () => requestMock.create({ method: 'put', - path: DETECTION_ENGINE_PREPACKAGED_URL, + path: PREBUILT_RULES_URL, }); export const getPrepackagedRulesStatusRequest = () => requestMock.create({ method: 'get', - path: `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`, + path: PREBUILT_RULES_STATUS_URL, }); export interface FindHit<T = RuleAlertType> { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts index 3ed486d9d9a1b..67462232a22ba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts @@ -7,10 +7,10 @@ import { Readable } from 'stream'; -import type { HapiReadableStream } from '../../rules/types'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; -import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; +import type { RuleResponse } from '../../../../../common/detection_engine/rule_schema'; +import type { HapiReadableStream } from '../../rule_management/logic/import/hapi_readable_stream'; /** * Given a string, builds a hapi stream as our @@ -34,7 +34,7 @@ export const buildHapiStream = (string: string, filename = 'file.ndjson'): HapiR return stream; }; -export const getOutputRuleAlertForRest = (): FullResponseSchema => ({ +export const getOutputRuleAlertForRest = (): RuleResponse => ({ author: ['Elastic'], actions: [], building_block_type: 'default', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts deleted file mode 100644 index 92a854b2540fe..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/get_prepackaged_rules_status_route.ts +++ /dev/null @@ -1,107 +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 { transformError } from '@kbn/securitysolution-es-utils'; -import { validate } from '@kbn/securitysolution-io-ts-utils'; -import type { PrePackagedRulesAndTimelinesStatusSchema } from '../../../../../common/detection_engine/schemas/response/prepackaged_rules_status_schema'; -import { prePackagedRulesAndTimelinesStatusSchema } from '../../../../../common/detection_engine/schemas/response/prepackaged_rules_status_schema'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_PREPACKAGED_URL } from '../../../../../common/constants'; -import { buildSiemResponse } from '../utils'; - -import { getRulesToInstall } from '../../rules/get_rules_to_install'; -import { getRulesToUpdate } from '../../rules/get_rules_to_update'; -import { findRules } from '../../rules/find_rules'; -import { getLatestPrepackagedRules } from '../../rules/get_prepackaged_rules'; -import { getExistingPrepackagedRules } from '../../rules/get_existing_prepackaged_rules'; -import { ruleAssetSavedObjectsClientFactory } from '../../rules/rule_asset/rule_asset_saved_objects_client'; -import { buildFrameworkRequest } from '../../../timeline/utils/common'; -import type { ConfigType } from '../../../../config'; -import type { SetupPlugins } from '../../../../plugin'; -import { - checkTimelinesStatus, - checkTimelineStatusRt, -} from '../../../timeline/utils/check_timelines_status'; -import { rulesToMap } from '../../rules/utils'; - -export const getPrepackagedRulesStatusRoute = ( - router: SecuritySolutionPluginRouter, - config: ConfigType, - security: SetupPlugins['security'] -) => { - router.get( - { - path: `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`, - validate: false, - options: { - tags: ['access:securitySolution'], - }, - }, - async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - const ctx = await context.resolve(['core', 'alerting']); - const savedObjectsClient = ctx.core.savedObjects.client; - const rulesClient = ctx.alerting.getRulesClient(); - const ruleAssetsClient = ruleAssetSavedObjectsClientFactory(savedObjectsClient); - - try { - const latestPrepackagedRules = await getLatestPrepackagedRules( - ruleAssetsClient, - config.prebuiltRulesFromFileSystem, - config.prebuiltRulesFromSavedObjects - ); - const customRules = await findRules({ - rulesClient, - perPage: 1, - page: 1, - sortField: 'enabled', - sortOrder: 'desc', - filter: 'alert.attributes.params.immutable: false', - fields: undefined, - }); - const frameworkRequest = await buildFrameworkRequest(context, security, request); - const installedPrePackagedRules = rulesToMap( - await getExistingPrepackagedRules({ rulesClient }) - ); - - const rulesToInstall = getRulesToInstall(latestPrepackagedRules, installedPrePackagedRules); - const rulesToUpdate = getRulesToUpdate(latestPrepackagedRules, installedPrePackagedRules); - const prepackagedTimelineStatus = await checkTimelinesStatus(frameworkRequest); - const [validatedPrepackagedTimelineStatus] = validate( - prepackagedTimelineStatus, - checkTimelineStatusRt - ); - - const prepackagedRulesStatus: PrePackagedRulesAndTimelinesStatusSchema = { - rules_custom_installed: customRules.total, - rules_installed: installedPrePackagedRules.size, - rules_not_installed: rulesToInstall.length, - rules_not_updated: rulesToUpdate.length, - timelines_installed: validatedPrepackagedTimelineStatus?.prepackagedTimelines.length ?? 0, - timelines_not_installed: - validatedPrepackagedTimelineStatus?.timelinesToInstall.length ?? 0, - timelines_not_updated: validatedPrepackagedTimelineStatus?.timelinesToUpdate.length ?? 0, - }; - const [validated, errors] = validate( - prepackagedRulesStatus, - prePackagedRulesAndTimelinesStatusSchema - ); - if (errors != null) { - return siemResponse.error({ statusCode: 500, body: errors }); - } else { - return response.ok({ body: validated ?? {} }); - } - } catch (err) { - const error = transformError(err); - return siemResponse.error({ - body: error.message, - statusCode: error.statusCode, - }); - } - } - ); -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.test.ts deleted file mode 100644 index 62c591abc5e9d..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.test.ts +++ /dev/null @@ -1,82 +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 { SavedObjectsClientContract } from '@kbn/core/server'; - -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; -import { findExceptionList } from '@kbn/lists-plugin/server/services/exception_lists/find_exception_list'; -import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; -import { getReferencedExceptionLists } from './gather_referenced_exceptions'; -import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; - -jest.mock('@kbn/lists-plugin/server/services/exception_lists/find_exception_list'); - -describe('getReferencedExceptionLists', () => { - let savedObjectsClient: jest.Mocked<SavedObjectsClientContract>; - - beforeEach(() => { - savedObjectsClient = savedObjectsClientMock.create(); - - (findExceptionList as jest.Mock).mockResolvedValue({ - data: [ - { - ...getExceptionListSchemaMock(), - id: '123', - list_id: 'my-list', - namespace_type: 'single', - type: 'detection', - }, - ], - page: 1, - per_page: 20, - total: 1, - }); - jest.clearAllMocks(); - }); - - it('returns empty object if no rules to search', async () => { - const result = await getReferencedExceptionLists({ - rules: [], - savedObjectsClient, - }); - - expect(result).toEqual({}); - }); - - it('returns found referenced exception lists', async () => { - const result = await getReferencedExceptionLists({ - rules: [ - { - ...getImportRulesSchemaMock(), - exceptions_list: [ - { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, - ], - }, - ], - savedObjectsClient, - }); - - expect(result).toEqual({ - 'my-list': { - ...getExceptionListSchemaMock(), - id: '123', - list_id: 'my-list', - namespace_type: 'single', - type: 'detection', - }, - }); - }); - - it('returns empty object if no referenced exception lists found', async () => { - const result = await getReferencedExceptionLists({ - rules: [], - savedObjectsClient, - }); - - expect(result).toEqual({}); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/legacy_create_legacy_notification.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/create_legacy_notification/route.ts similarity index 90% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/legacy_create_legacy_notification.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/create_legacy_notification/route.ts index e6d98ed4f8195..5b20d410db8ce 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/legacy_create_legacy_notification.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/create_legacy_notification/route.ts @@ -8,15 +8,15 @@ import { schema } from '@kbn/config-schema'; import type { Logger } from '@kbn/core/server'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; // eslint-disable-next-line no-restricted-imports -import { legacyUpdateOrCreateRuleActionsSavedObject } from '../../rule_actions/legacy_update_or_create_rule_actions_saved_object'; +import { legacyUpdateOrCreateRuleActionsSavedObject } from '../../logic/rule_actions/legacy_update_or_create_rule_actions_saved_object'; // eslint-disable-next-line no-restricted-imports -import { legacyReadNotifications } from '../../notifications/legacy_read_notifications'; +import { legacyReadNotifications } from '../../logic/notifications/legacy_read_notifications'; // eslint-disable-next-line no-restricted-imports -import type { LegacyRuleNotificationAlertTypeParams } from '../../notifications/legacy_types'; +import type { LegacyRuleNotificationAlertTypeParams } from '../../logic/notifications/legacy_types'; // eslint-disable-next-line no-restricted-imports -import { legacyCreateNotifications } from '../../notifications/legacy_create_notifications'; +import { legacyCreateNotifications } from '../../logic/notifications/legacy_create_notifications'; /** * Given an "alert_id" and a valid "action_id" this will create a legacy notification. This is for testing diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/register_routes.ts new file mode 100644 index 0000000000000..aa4010691a71b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/api/register_routes.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. + */ + +import type { Logger } from '@kbn/core/server'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; + +// eslint-disable-next-line no-restricted-imports +import { legacyCreateLegacyNotificationRoute } from './create_legacy_notification/route'; + +export const registerLegacyRuleActionsRoutes = ( + router: SecuritySolutionPluginRouter, + logger: Logger +) => { + // Once we no longer have the legacy notifications system/"side car actions" this should be removed. + legacyCreateLegacyNotificationRoute(router, logger); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/index.ts new file mode 100644 index 0000000000000..22d3c347eef85 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/index.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. + */ + +export * from './api/register_routes'; + +// eslint-disable-next-line no-restricted-imports +export { legacyRulesNotificationAlertType } from './logic/notifications/legacy_rules_notification_alert_type'; +// eslint-disable-next-line no-restricted-imports +export { legacyIsNotificationAlertExecutor } from './logic/notifications/legacy_types'; +// eslint-disable-next-line no-restricted-imports +export type { + LegacyRuleNotificationAlertType, + LegacyRuleNotificationAlertTypeParams, +} from './logic/notifications/legacy_types'; +export type { NotificationRuleTypeParams } from './logic/notifications/schedule_notification_actions'; +export { scheduleNotificationActions } from './logic/notifications/schedule_notification_actions'; +export { scheduleThrottledNotificationActions } from './logic/notifications/schedule_throttle_notification_actions'; +export { getNotificationResultsLink } from './logic/notifications/utils'; + +// eslint-disable-next-line no-restricted-imports +export { legacyGetBulkRuleActionsSavedObject } from './logic/rule_actions/legacy_get_bulk_rule_actions_saved_object'; +// eslint-disable-next-line no-restricted-imports +export type { LegacyRulesActionsSavedObject } from './logic/rule_actions/legacy_get_rule_actions_saved_object'; +// eslint-disable-next-line no-restricted-imports +export { legacyGetRuleActionsSavedObject } from './logic/rule_actions/legacy_get_rule_actions_saved_object'; +// eslint-disable-next-line no-restricted-imports +export { + legacyType, + legacyRuleActionsSavedObjectType, +} from './logic/rule_actions/legacy_saved_object_mappings'; +// eslint-disable-next-line no-restricted-imports +export type { + LegacyRuleActions, + LegacyRuleAlertAction, + LegacyIRuleActionsAttributes, + LegacyRuleAlertSavedObjectAction, + LegacyIRuleActionsAttributesSavedObjectAttributes, +} from './logic/rule_actions/legacy_types'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/build_signals_query.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/build_signals_query.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/build_signals_query.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/build_signals_query.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/build_signals_query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/build_signals_query.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/build_signals_query.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/build_signals_query.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/get_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/get_signals.ts similarity index 92% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/get_signals.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/get_signals.ts index ab202170e5764..5963cc0cb2211 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/get_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/get_signals.ts @@ -6,7 +6,7 @@ */ import type { ElasticsearchClient } from '@kbn/core/server'; -import type { SignalSearchResponse, SignalSource } from '../signals/types'; +import type { SignalSearchResponse, SignalSource } from '../../../signals/types'; import { buildSignalsSearchQuery } from './build_signals_query'; interface GetSignalsParams { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_create_notifications.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_create_notifications.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_create_notifications.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_create_notifications.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_create_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_create_notifications.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_create_notifications.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_create_notifications.ts index 5db2759ad88da..983519404b222 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_create_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_create_notifications.ts @@ -6,7 +6,7 @@ */ import type { SanitizedRule } from '@kbn/alerting-plugin/common'; -import { SERVER_APP_ID, LEGACY_NOTIFICATIONS_ID } from '../../../../common/constants'; +import { SERVER_APP_ID, LEGACY_NOTIFICATIONS_ID } from '../../../../../../common/constants'; // eslint-disable-next-line no-restricted-imports import type { CreateNotificationParams, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_find_notifications.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_find_notifications.test.ts similarity index 91% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_find_notifications.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_find_notifications.test.ts index 32a1b0eacd55b..d5ebddf4d2b63 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_find_notifications.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_find_notifications.test.ts @@ -7,7 +7,7 @@ // eslint-disable-next-line no-restricted-imports import { legacyGetFilter } from './legacy_find_notifications'; -import { LEGACY_NOTIFICATIONS_ID } from '../../../../common/constants'; +import { LEGACY_NOTIFICATIONS_ID } from '../../../../../../common/constants'; describe('legacyFind_notifications', () => { test('it returns a full filter with an AND if sent down', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_find_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_find_notifications.ts similarity index 94% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_find_notifications.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_find_notifications.ts index ba2f3e54dd9dc..0f981e6c6a005 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_find_notifications.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_find_notifications.ts @@ -6,7 +6,7 @@ */ import type { RuleTypeParams, FindResult } from '@kbn/alerting-plugin/server'; -import { LEGACY_NOTIFICATIONS_ID } from '../../../../common/constants'; +import { LEGACY_NOTIFICATIONS_ID } from '../../../../../../common/constants'; // eslint-disable-next-line no-restricted-imports import type { LegacyFindNotificationParams } from './legacy_types'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_read_notifications.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_read_notifications.test.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_read_notifications.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_read_notifications.test.ts index 0980159d67882..f39881ac63250 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_read_notifications.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_read_notifications.test.ts @@ -11,7 +11,7 @@ import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; import { legacyGetNotificationResult, legacyGetFindNotificationsResultWithSingleHit, -} from '../routes/__mocks__/request_responses'; +} from '../../../routes/__mocks__/request_responses'; class LegacyTestError extends Error { constructor() { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_read_notifications.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_read_notifications.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_read_notifications.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_read_notifications.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_alert_type.test.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_alert_type.test.ts index b79e8ac4cbbdb..ddfca67279074 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_alert_type.test.ts @@ -9,7 +9,7 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import type { RuleExecutorServicesMock } from '@kbn/alerting-plugin/server/mocks'; import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; -import { getRuleMock } from '../routes/__mocks__/request_responses'; +import { getRuleMock } from '../../../routes/__mocks__/request_responses'; // eslint-disable-next-line no-restricted-imports import { legacyRulesNotificationAlertType } from './legacy_rules_notification_alert_type'; import { buildSignalsSearchQuery } from './build_signals_query'; @@ -19,9 +19,9 @@ import { sampleDocSearchResultsNoSortIdNoVersion, sampleDocSearchResultsWithSortId, sampleEmptyDocSearchResults, -} from '../signals/__mocks__/es_results'; -import { DEFAULT_RULE_NOTIFICATION_QUERY_SIZE } from '../../../../common/constants'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; +} from '../../../signals/__mocks__/es_results'; +import { DEFAULT_RULE_NOTIFICATION_QUERY_SIZE } from '../../../../../../common/constants'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; jest.mock('./build_signals_query'); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_alert_type.ts similarity index 96% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_alert_type.ts index 880882df04af2..7e76c2c1158d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_rules_notification_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_rules_notification_alert_type.ts @@ -11,14 +11,14 @@ import { DEFAULT_RULE_NOTIFICATION_QUERY_SIZE, LEGACY_NOTIFICATIONS_ID, SERVER_APP_ID, -} from '../../../../common/constants'; +} from '../../../../../../common/constants'; // eslint-disable-next-line no-restricted-imports import type { LegacyNotificationAlertTypeDefinition } from './legacy_types'; // eslint-disable-next-line no-restricted-imports import { legacyRulesNotificationParams } from './legacy_types'; -import type { AlertAttributes } from '../signals/types'; -import { siemRuleActionGroups } from '../signals/siem_rule_action_groups'; +import type { AlertAttributes } from '../../../signals/types'; +import { siemRuleActionGroups } from '../../../signals/siem_rule_action_groups'; import { scheduleNotificationActions } from './schedule_notification_actions'; import { getNotificationResultsLink } from './utils'; import { getSignals } from './get_signals'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/README.md similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/README.md rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/README.md diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_extract_references.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_references.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_extract_references.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_extract_references.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_references.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_extract_references.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_rule_id.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_extract_rule_id.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_rule_id.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_extract_rule_id.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_rule_id.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_extract_rule_id.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_extract_rule_id.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_extract_rule_id.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_inject_references.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_references.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_inject_references.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_inject_references.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_references.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_inject_references.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_saved_object_references/legacy_inject_rule_id_references.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_types.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_types.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_types.ts index a3516d2caa7f0..2aea76d8c51e1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/legacy_types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/legacy_types.ts @@ -19,7 +19,7 @@ import type { RuleExecutorOptions, } from '@kbn/alerting-plugin/server'; import type { Rule, RuleAction } from '@kbn/alerting-plugin/common'; -import { LEGACY_NOTIFICATIONS_ID } from '../../../../common/constants'; +import { LEGACY_NOTIFICATIONS_ID } from '../../../../../../common/constants'; /** * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_notification_actions.test.ts similarity index 94% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_notification_actions.test.ts index 05741547241a3..b82136e33acf2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_notification_actions.test.ts @@ -7,9 +7,9 @@ import type { RuleExecutorServicesMock } from '@kbn/alerting-plugin/server/mocks'; import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; -import type { DetectionAlert } from '../../../../common/detection_engine/schemas/alerts'; -import { ALERT_THRESHOLD_RESULT_COUNT } from '../../../../common/field_maps/field_names'; -import { sampleThresholdAlert } from '../rule_types/__mocks__/threshold'; +import type { DetectionAlert } from '../../../../../../common/detection_engine/schemas/alerts'; +import { ALERT_THRESHOLD_RESULT_COUNT } from '../../../../../../common/field_maps/field_names'; +import { sampleThresholdAlert } from '../../../rule_types/__mocks__/threshold'; import type { NotificationRuleTypeParams } from './schedule_notification_actions'; import { formatAlertsForNotificationActions, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_notification_actions.ts similarity index 81% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_notification_actions.ts index 7783ca0e1ede8..8ca2bc0a4fb01 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_notification_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_notification_actions.ts @@ -9,13 +9,13 @@ import { mapKeys, snakeCase } from 'lodash/fp'; import type { Alert } from '@kbn/alerting-plugin/server'; import { ALERT_RULE_TYPE } from '@kbn/rule-data-utils'; import { flattenWithPrefix } from '@kbn/securitysolution-rules'; -import { ALERT_THRESHOLD_RESULT } from '../../../../common/field_maps/field_names'; -import { isThresholdRule } from '../../../../common/detection_engine/utils'; -import { expandDottedObject } from '../../../../common/utils/expand_dotted'; -import type { RuleParams } from '../schemas/rule_schemas'; -import aadFieldConversion from '../routes/index/signal_aad_mapping.json'; -import { isDetectionAlert } from '../signals/utils'; -import type { DetectionAlert } from '../../../../common/detection_engine/schemas/alerts'; +import { ALERT_THRESHOLD_RESULT } from '../../../../../../common/field_maps/field_names'; +import { isThresholdRule } from '../../../../../../common/detection_engine/utils'; +import { expandDottedObject } from '../../../../../../common/utils/expand_dotted'; +import type { RuleParams } from '../../../rule_schema'; +import aadFieldConversion from '../../../routes/index/signal_aad_mapping.json'; +import { isDetectionAlert } from '../../../signals/utils'; +import type { DetectionAlert } from '../../../../../../common/detection_engine/schemas/alerts'; export type NotificationRuleTypeParams = RuleParams & { id: string; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_throttle_notification_actions.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_throttle_notification_actions.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_throttle_notification_actions.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_throttle_notification_actions.ts index 90bc88a7ccb56..98f5ee9d42557 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/schedule_throttle_notification_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_throttle_notification_actions.ts @@ -8,13 +8,13 @@ import type { ElasticsearchClient, SavedObject, Logger } from '@kbn/core/server'; import { parseScheduleDates } from '@kbn/securitysolution-io-ts-utils'; import type { Alert } from '@kbn/alerting-plugin/server'; -import type { RuleParams } from '../schemas/rule_schemas'; +import type { RuleParams } from '../../../rule_schema'; import { deconflictSignalsAndResults, getNotificationResultsLink } from './utils'; -import { DEFAULT_RULE_NOTIFICATION_QUERY_SIZE } from '../../../../common/constants'; +import { DEFAULT_RULE_NOTIFICATION_QUERY_SIZE } from '../../../../../../common/constants'; import { getSignals } from './get_signals'; import type { NotificationRuleTypeParams } from './schedule_notification_actions'; import { scheduleNotificationActions } from './schedule_notification_actions'; -import type { AlertAttributes } from '../signals/types'; +import type { AlertAttributes } from '../../../signals/types'; interface ScheduleThrottledNotificationActionsOptions { id: SavedObject['id']; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/utils.test.ts similarity index 99% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/utils.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/utils.test.ts index d281cc9ae977b..cb3c4e0d4bd41 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/utils.test.ts @@ -7,7 +7,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import type { SignalSource } from '../signals/types'; +import type { SignalSource } from '../../../signals/types'; import { deconflictSignalsAndResults, getNotificationResultsLink } from './utils'; describe('utils', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/utils.ts similarity index 95% rename from x-pack/plugins/security_solution/server/lib/detection_engine/notifications/utils.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/utils.ts index 560ec11cbef19..9eef538efe859 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/notifications/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/utils.ts @@ -6,8 +6,8 @@ */ import type { Logger } from '@kbn/core/server'; -import { APP_PATH } from '../../../../common/constants'; -import type { SignalSearchResponse } from '../signals/types'; +import { APP_PATH } from '../../../../../../common/constants'; +import type { SignalSearchResponse } from '../../../signals/types'; export const getNotificationResultsLink = ({ kibanaSiemAppUrl = APP_PATH, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_create_rule_actions_saved_object.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_create_rule_actions_saved_object.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_create_rule_actions_saved_object.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_create_rule_actions_saved_object.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_create_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_create_rule_actions_saved_object.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_create_rule_actions_saved_object.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_create_rule_actions_saved_object.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts index e827bd4fe14e5..300d5307c1773 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_bulk_rule_actions_saved_object.ts @@ -17,7 +17,7 @@ import type { LegacyIRuleActionsAttributesSavedObjectAttributes } from './legacy import { legacyGetRuleActionsFromSavedObject } from './legacy_utils'; // eslint-disable-next-line no-restricted-imports import type { LegacyRulesActionsSavedObject } from './legacy_get_rule_actions_saved_object'; -import { initPromisePool } from '../../../utils/promise_pool'; +import { initPromisePool } from '../../../../../utils/promise_pool'; /** * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_rule_actions_saved_object.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_rule_actions_saved_object.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_rule_actions_saved_object.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_rule_actions_saved_object.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_rule_actions_saved_object.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_get_rule_actions_saved_object.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_get_rule_actions_saved_object.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_migrations.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_migrations.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_saved_object_mappings.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_saved_object_mappings.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_saved_object_mappings.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_saved_object_mappings.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_types.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_types.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_types.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_or_create_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_update_or_create_rule_actions_saved_object.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_or_create_rule_actions_saved_object.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_update_or_create_rule_actions_saved_object.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_rule_actions_saved_object.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_update_rule_actions_saved_object.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_update_rule_actions_saved_object.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_update_rule_actions_saved_object.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_utils.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_utils.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_utils.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions/legacy_utils.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/rule_actions/legacy_utils.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rule_exceptions_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.test.ts similarity index 94% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rule_exceptions_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.test.ts index a65d165f9e109..ab7efc936a58a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rule_exceptions_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.test.ts @@ -7,11 +7,11 @@ import { getDetectionsExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import { getRuleMock, resolveRuleMock } from '../__mocks__/request_responses'; -import { requestContextMock, serverMock, requestMock } from '../__mocks__'; -import { createRuleExceptionsRoute } from './create_rule_exceptions_route'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../common/constants'; +import { getRuleMock, resolveRuleMock } from '../../../routes/__mocks__/request_responses'; +import { requestContextMock, serverMock, requestMock } from '../../../routes/__mocks__'; +import { createRuleExceptionsRoute } from './route'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; const getMockExceptionItem = () => ({ description: 'Exception item for rule default exception list', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rule_exceptions_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts similarity index 86% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rule_exceptions_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts index 8b5c3e1b044f2..9a13833e08437 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rule_exceptions_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/create_rule_exceptions/route.ts @@ -27,35 +27,38 @@ import { formatErrors, validate } from '@kbn/securitysolution-io-ts-utils'; import type { SanitizedRule } from '@kbn/alerting-plugin/common'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { RulesClient } from '@kbn/alerting-plugin/server'; + import type { - CreateRuleExceptionSchemaDecoded, - QueryRuleByIdSchemaDecoded, -} from '../../../../../common/detection_engine/schemas/request'; + CreateRuleExceptionsRequestBodyDecoded, + CreateRuleExceptionsRequestParamsDecoded, +} from '../../../../../../common/detection_engine/rule_exceptions'; import { - createRuleExceptionsSchema, - queryRuleByIdSchema, -} from '../../../../../common/detection_engine/schemas/request'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import { buildSiemResponse } from '../utils'; -import { patchRules } from '../../rules/patch_rules'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { readRules } from '../../rules/read_rules'; -import type { RuleParams } from '../../schemas/rule_schemas'; -import { checkDefaultRuleExceptionListReferences } from './utils/check_for_default_rule_exception_list'; + CREATE_RULE_EXCEPTIONS_URL, + CreateRuleExceptionsRequestBody, + CreateRuleExceptionsRequestParams, +} from '../../../../../../common/detection_engine/rule_exceptions'; + +import { readRules } from '../../../rule_management/logic/crud/read_rules'; +import { patchRules } from '../../../rule_management/logic/crud/patch_rules'; +import { checkDefaultRuleExceptionListReferences } from '../../../rule_management/logic/exceptions/check_for_default_rule_exception_list'; +import type { RuleParams } from '../../../rule_schema'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; +import { buildSiemResponse } from '../../../routes/utils'; +import { buildRouteValidation } from '../../../../../utils/build_validation/route_validation'; export const createRuleExceptionsRoute = (router: SecuritySolutionPluginRouter) => { router.post( { - path: `${DETECTION_ENGINE_RULES_URL}/{id}/exceptions`, + path: CREATE_RULE_EXCEPTIONS_URL, validate: { - params: buildRouteValidation<typeof queryRuleByIdSchema, QueryRuleByIdSchemaDecoded>( - queryRuleByIdSchema - ), + params: buildRouteValidation< + typeof CreateRuleExceptionsRequestParams, + CreateRuleExceptionsRequestParamsDecoded + >(CreateRuleExceptionsRequestParams), body: buildRouteValidation< - typeof createRuleExceptionsSchema, - CreateRuleExceptionSchemaDecoded - >(createRuleExceptionsSchema), + typeof CreateRuleExceptionsRequestBody, + CreateRuleExceptionsRequestBodyDecoded + >(CreateRuleExceptionsRequestBody), }, options: { tags: ['access:securitySolution'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_exceptions_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.test.ts similarity index 95% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_exceptions_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.test.ts index 39d2dca25f050..33e66bb6405f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_exceptions_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.test.ts @@ -5,16 +5,18 @@ * 2.0. */ -import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '../../../../../common/constants'; +import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; + +import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '../../../../../../common/detection_engine/rule_exceptions'; + import { getEmptyFindResult, getFindResultWithSingleHit, getRuleMock, -} from '../__mocks__/request_responses'; -import { requestContextMock, serverMock, requestMock } from '../__mocks__'; -import { findRuleExceptionReferencesRoute } from './find_rule_exceptions_route'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; -import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; +} from '../../../routes/__mocks__/request_responses'; +import { requestContextMock, serverMock, requestMock } from '../../../routes/__mocks__'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; +import { findRuleExceptionReferencesRoute } from './route'; describe('findRuleExceptionReferencesRoute', () => { let server: ReturnType<typeof serverMock.create>; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_exceptions_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.ts similarity index 79% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_exceptions_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.ts index 8d1dc2cc7c49e..a9643989a3a3b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rule_exceptions_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/find_exception_references/route.ts @@ -9,16 +9,22 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { getSavedObjectType } from '@kbn/securitysolution-list-utils'; import { validate } from '@kbn/securitysolution-io-ts-utils'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '../../../../../common/constants'; -import { buildSiemResponse } from '../utils'; -import { enrichFilterWithRuleTypeMapping } from '../../rules/enrich_filter_with_rule_type_mappings'; -import type { FindExceptionReferencesOnRuleSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/find_exception_list_references_schema'; -import { findExceptionReferencesOnRuleSchema } from '../../../../../common/detection_engine/schemas/request/find_exception_list_references_schema'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import type { RuleReferencesSchema } from '../../../../../common/detection_engine/schemas/response/find_exception_list_references_schema'; -import { rulesReferencedByExceptionListsSchema } from '../../../../../common/detection_engine/schemas/response/find_exception_list_references_schema'; -import type { RuleParams } from '../../schemas/rule_schemas'; +import { buildRouteValidation } from '../../../../../utils/build_validation/route_validation'; +import { buildSiemResponse } from '../../../routes/utils'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; + +import type { + FindExceptionReferencesOnRuleSchemaDecoded, + RuleReferencesSchema, +} from '../../../../../../common/detection_engine/rule_exceptions'; +import { + DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL, + findExceptionReferencesOnRuleSchema, + rulesReferencedByExceptionListsSchema, +} from '../../../../../../common/detection_engine/rule_exceptions'; + +import { enrichFilterWithRuleTypeMapping } from '../../../rule_management/logic/search/enrich_filter_with_rule_type_mappings'; +import type { RuleParams } from '../../../rule_schema'; export const findRuleExceptionReferencesRoute = (router: SecuritySolutionPluginRouter) => { router.get( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/register_routes.ts new file mode 100644 index 0000000000000..4d65f13c94ee2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/api/register_routes.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SecuritySolutionPluginRouter } from '../../../../types'; + +import { createRuleExceptionsRoute } from './create_rule_exceptions/route'; +import { findRuleExceptionReferencesRoute } from './find_exception_references/route'; + +export const registerRuleExceptionsRoutes = (router: SecuritySolutionPluginRouter) => { + createRuleExceptionsRoute(router); + findRuleExceptionReferencesRoute(router); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/index.ts new file mode 100644 index 0000000000000..c0123e587d9bf --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_exceptions/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 * from './api/register_routes'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/deprecation.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/deprecation.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/deprecation.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/deprecation.ts index 314d7db33fcef..8e956e0e48aa6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/deprecation.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/deprecation.ts @@ -7,7 +7,7 @@ import { getDocLinks } from '@kbn/doc-links'; import type { Logger } from '@kbn/core/server'; -import { DETECTION_ENGINE_RULES_BULK_ACTION } from '../../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_BULK_ACTION } from '../../../../../common/constants'; /** * Helper method for building deprecation messages diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts new file mode 100644 index 0000000000000..90a37a7dbe7da --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; +import type { ConfigType } from '../../../../config'; +import type { SetupPlugins } from '../../../../plugin_contract'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; + +import { performBulkActionRoute } from './rules/bulk_actions/route'; +import { bulkCreateRulesRoute } from './rules/bulk_create_rules/route'; +import { bulkDeleteRulesRoute } from './rules/bulk_delete_rules/route'; +import { bulkPatchRulesRoute } from './rules/bulk_patch_rules/route'; +import { bulkUpdateRulesRoute } from './rules/bulk_update_rules/route'; +import { createRuleRoute } from './rules/create_rule/route'; +import { deleteRuleRoute } from './rules/delete_rule/route'; +import { exportRulesRoute } from './rules/export_rules/route'; +import { findRulesRoute } from './rules/find_rules/route'; +import { importRulesRoute } from './rules/import_rules/route'; +import { patchRuleRoute } from './rules/patch_rule/route'; +import { readRuleRoute } from './rules/read_rule/route'; +import { updateRuleRoute } from './rules/update_rule/route'; +import { readTagsRoute } from './tags/read_tags/route'; + +export const registerRuleManagementRoutes = ( + router: SecuritySolutionPluginRouter, + config: ConfigType, + ml: SetupPlugins['ml'], + logger: Logger +) => { + // Rules CRUD + createRuleRoute(router, ml); + readRuleRoute(router, logger); + updateRuleRoute(router, ml); + patchRuleRoute(router, ml); + deleteRuleRoute(router); + + // Rules bulk CRUD + bulkCreateRulesRoute(router, ml, logger); + bulkUpdateRulesRoute(router, ml, logger); + bulkPatchRulesRoute(router, ml, logger); + bulkDeleteRulesRoute(router, logger); + + // Rules bulk actions + performBulkActionRoute(router, ml, logger); + + // Rules export/import + exportRulesRoute(router, config, logger); + importRulesRoute(router, config, ml); + + // Rules search + findRulesRoute(router, logger); + + // Rule tags + readTagsRoute(router); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts similarity index 94% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts index 1438905a5c079..f64de099dfcbe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.test.ts @@ -8,9 +8,9 @@ import { DETECTION_ENGINE_RULES_BULK_ACTION, BulkActionsDryRunErrCode, -} from '../../../../../common/constants'; -import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; -import { buildMlAuthz } from '../../../machine_learning/authz'; +} from '../../../../../../../common/constants'; +import { mlServicesMock } from '../../../../../machine_learning/mocks'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { getEmptyFindResult, getBulkActionRequest, @@ -18,30 +18,31 @@ import { getFindResultWithSingleHit, getFindResultWithMultiHits, getRuleMock, -} from '../__mocks__/request_responses'; -import { requestContextMock, serverMock, requestMock } from '../__mocks__'; -import { performBulkActionRoute } from './perform_bulk_action_route'; +} from '../../../../routes/__mocks__/request_responses'; +import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; +import { performBulkActionRoute } from './route'; import { getPerformBulkActionEditSchemaMock, getPerformBulkActionSchemaMock, -} from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema.mock'; +} from '../../../../../../../common/detection_engine/rule_management/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { readRules } from '../../rules/read_rules'; -import { legacyMigrate } from '../../rules/utils'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { readRules } from '../../../logic/crud/read_rules'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; +import { getQueryRuleParams } from '../../../../rule_schema/mocks'; -jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); -jest.mock('../../rules/read_rules', () => ({ readRules: jest.fn() })); +jest.mock('../../../../../machine_learning/authz'); +jest.mock('../../../logic/crud/read_rules', () => ({ readRules: jest.fn() })); -jest.mock('../../rules/utils', () => { - const actual = jest.requireActual('../../rules/utils'); +jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { + const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); return { ...actual, legacyMigrate: jest.fn(), }; }); -describe('perform_bulk_action', () => { +describe('Perform bulk action route', () => { const readRulesMock = readRules as jest.Mock; let server: ReturnType<typeof serverMock.create>; let { clients, context } = requestContextMock.createTools(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts similarity index 90% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts index 56d93a1ed0336..67a9a233eb807 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/perform_bulk_action_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts @@ -13,43 +13,43 @@ import type { KibanaResponseFactory, Logger, SavedObjectsClientContract } from ' import type { RulesClient, BulkEditError } from '@kbn/alerting-plugin/server'; import type { SanitizedRule } from '@kbn/alerting-plugin/common'; import { AbortError } from '@kbn/kibana-utils-plugin/common'; -import type { RuleAlertType } from '../../rules/types'; +import type { RuleAlertType, RuleParams } from '../../../../rule_schema'; -import type { BulkActionsDryRunErrCode } from '../../../../../common/constants'; +import type { BulkActionsDryRunErrCode } from '../../../../../../../common/constants'; import { DETECTION_ENGINE_RULES_BULK_ACTION, MAX_RULES_TO_UPDATE_IN_PARALLEL, RULES_TABLE_MAX_PAGE_SIZE, -} from '../../../../../common/constants'; +} from '../../../../../../../common/constants'; import { - performBulkActionSchema, - performBulkActionQuerySchema, BulkAction, -} from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import type { SetupPlugins } from '../../../../plugin'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { routeLimitedConcurrencyTag } from '../../../../utils/route_limited_concurrency_tag'; -import type { PromisePoolError, PromisePoolOutcome } from '../../../../utils/promise_pool'; -import { initPromisePool } from '../../../../utils/promise_pool'; -import { buildMlAuthz } from '../../../machine_learning/authz'; -import { deleteRules } from '../../rules/delete_rules'; -import { duplicateRule } from '../../rules/duplicate_rule'; -import { findRules } from '../../rules/find_rules'; -import { readRules } from '../../rules/read_rules'; -import { bulkEditRules } from '../../rules/bulk_edit_rules'; -import { getExportByObjectIds } from '../../rules/get_export_by_object_ids'; -import { buildSiemResponse } from '../utils'; -import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; -import { legacyMigrate } from '../../rules/utils'; -import type { DryRunError } from '../../rules/bulk_actions/dry_run'; + PerformBulkActionRequestBody, + PerformBulkActionRequestQuery, +} from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import type { SetupPlugins } from '../../../../../../plugin'; +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import { routeLimitedConcurrencyTag } from '../../../../../../utils/route_limited_concurrency_tag'; +import type { PromisePoolError, PromisePoolOutcome } from '../../../../../../utils/promise_pool'; +import { initPromisePool } from '../../../../../../utils/promise_pool'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import { deleteRules } from '../../../logic/crud/delete_rules'; +import { duplicateRule } from '../../../logic/actions/duplicate_rule'; +import { findRules } from '../../../logic/search/find_rules'; +import { readRules } from '../../../logic/crud/read_rules'; +import { getExportByObjectIds } from '../../../logic/export/get_export_by_object_ids'; +import { buildSiemResponse } from '../../../../routes/utils'; +import { internalRuleToAPIResponse } from '../../../normalization/rule_converters'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; +import { bulkEditRules } from '../../../logic/bulk_actions/bulk_edit_rules'; +import type { DryRunError } from '../../../logic/bulk_actions/dry_run'; import { validateBulkEnableRule, validateBulkDisableRule, validateBulkDuplicateRule, dryRunValidateBulkEditRule, -} from '../../rules/bulk_actions/validations'; -import type { RuleParams } from '../../schemas/rule_schemas'; +} from '../../../logic/bulk_actions/validations'; const MAX_RULES_TO_PROCESS_TOTAL = 10000; const MAX_ERROR_MESSAGE_LENGTH = 1000; @@ -267,10 +267,8 @@ export const performBulkActionRoute = ( { path: DETECTION_ENGINE_RULES_BULK_ACTION, validate: { - body: buildRouteValidation<typeof performBulkActionSchema>(performBulkActionSchema), - query: buildRouteValidation<typeof performBulkActionQuerySchema>( - performBulkActionQuerySchema - ), + body: buildRouteValidation(PerformBulkActionRequestBody), + query: buildRouteValidation(PerformBulkActionRequestQuery), }, options: { tags: ['access:securitySolution', routeLimitedConcurrencyTag(MAX_ROUTE_CONCURRENCY)], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.test.ts new file mode 100644 index 0000000000000..bc15b93639cdb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.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 type { BulkCreateRulesRequestBody } from '../../../../../../../common/detection_engine/rule_management'; +import { getDuplicates } from './get_duplicates'; + +describe('getDuplicates', () => { + test("returns array of ruleIds showing the duplicate keys of 'value2' and 'value3'", () => { + const output = getDuplicates( + [ + { rule_id: 'value1' }, + { rule_id: 'value2' }, + { rule_id: 'value2' }, + { rule_id: 'value3' }, + { rule_id: 'value3' }, + {}, + {}, + ] as BulkCreateRulesRequestBody, + 'rule_id' + ); + const expected = ['value2', 'value3']; + expect(output).toEqual(expected); + }); + + test('returns null when given a map of no duplicates', () => { + const output = getDuplicates( + [ + { rule_id: 'value1' }, + { rule_id: 'value2' }, + { rule_id: 'value3' }, + {}, + {}, + ] as BulkCreateRulesRequestBody, + 'rule_id' + ); + const expected: string[] = []; + expect(output).toEqual(expected); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.ts new file mode 100644 index 0000000000000..3d537bbd25c46 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/get_duplicates.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 { countBy } from 'lodash/fp'; +import type { BulkCreateRulesRequestBody } from '../../../../../../../common/detection_engine/rule_management'; + +export const getDuplicates = ( + ruleDefinitions: BulkCreateRulesRequestBody, + by: 'rule_id' +): string[] => { + const mappedDuplicates = countBy( + by, + ruleDefinitions.filter((r) => r[by] != null) + ); + const hasDuplicates = Object.values(mappedDuplicates).some((i) => i > 1); + if (hasDuplicates) { + return Object.keys(mappedDuplicates).filter((key) => mappedDuplicates[key] > 1); + } + return []; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts similarity index 90% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts index c845f2e0a381c..1dcb5e885d72b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../common/constants'; -import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; -import { buildMlAuthz } from '../../../machine_learning/authz'; +import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../../../common/constants'; +import { mlServicesMock } from '../../../../../machine_learning/mocks'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { getReadBulkRequest, getFindResultWithSingleHit, @@ -16,17 +16,17 @@ import { createBulkMlRuleRequest, getBasicEmptySearchResponse, getBasicNoShardsSearchResponse, -} from '../__mocks__/request_responses'; -import { requestContextMock, serverMock, requestMock } from '../__mocks__'; -import { createRulesBulkRoute } from './create_rules_bulk_route'; -import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; +} from '../../../../routes/__mocks__/request_responses'; +import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; +import { bulkCreateRulesRoute } from './route'; +import { getCreateRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_schema/mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { getQueryRuleParams } from '../../../../rule_schema/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); +jest.mock('../../../../../machine_learning/authz'); -describe('create_rules_bulk', () => { +describe('Bulk create rules route', () => { let server: ReturnType<typeof serverMock.create>; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType<typeof mlServicesMock.createSetupContract>; @@ -43,7 +43,7 @@ describe('create_rules_bulk', () => { context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); - createRulesBulkRoute(server.router, ml, logger); + bulkCreateRulesRoute(server.router, ml, logger); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts similarity index 73% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts index b6bdba6fca3bd..960ddb15c5301 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts @@ -5,29 +5,37 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { Logger } from '@kbn/core/server'; -import { createRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/create_rules_type_dependents'; -import { createRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; -import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../common/constants'; -import type { SetupPlugins } from '../../../../plugin'; -import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwAuthzError } from '../../../machine_learning/validation'; -import { readRules } from '../../rules/read_rules'; -import { getDuplicates } from './utils'; -import { transformValidateBulkError } from './validate'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; +import { validate } from '@kbn/securitysolution-io-ts-utils'; + +import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../../../common/constants'; +import { + BulkCreateRulesRequestBody, + validateCreateRuleProps, + BulkCrudRulesResponse, +} from '../../../../../../../common/detection_engine/rule_management'; + +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import type { SetupPlugins } from '../../../../../../plugin'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import { throwAuthzError } from '../../../../../machine_learning/validation'; +import { createRules } from '../../../logic/crud/create_rules'; +import { readRules } from '../../../logic/crud/read_rules'; +import { getDuplicates } from './get_duplicates'; +import { transformValidateBulkError } from '../../../utils/validate'; +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; -import { transformBulkError, createBulkErrorObject, buildSiemResponse } from '../utils'; -import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation'; -import { createRules } from '../../rules/create_rules'; +import { + transformBulkError, + createBulkErrorObject, + buildSiemResponse, +} from '../../../../routes/utils'; +import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../../deprecation'; /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const createRulesBulkRoute = ( +export const bulkCreateRulesRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], logger: Logger @@ -36,7 +44,7 @@ export const createRulesBulkRoute = ( { path: DETECTION_ENGINE_RULES_BULK_CREATE, validate: { - body: buildRouteValidation(createRulesBulkSchema), + body: buildRouteValidation(BulkCreateRulesRequestBody), }, options: { tags: ['access:securitySolution'], @@ -82,7 +90,7 @@ export const createRulesBulkRoute = ( } try { - const validationErrors = createRuleValidateTypeDependents(payloadRule); + const validationErrors = validateCreateRuleProps(payloadRule); if (validationErrors.length) { return createBulkErrorObject({ ruleId: payloadRule.rule_id, @@ -117,7 +125,7 @@ export const createRulesBulkRoute = ( }) ), ]; - const [validated, errors] = validate(rulesBulk, rulesBulkSchema); + const [validated, errors] = validate(rulesBulk, BulkCrudRulesResponse); if (errors != null) { return siemResponse.error({ statusCode: 500, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.test.ts similarity index 95% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.test.ts index 01683599e4cf6..750419c464b2a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../../../common/constants'; import { getEmptyFindResult, getFindResultWithSingleHit, @@ -14,12 +14,12 @@ import { getDeleteAsPostBulkRequest, getDeleteAsPostBulkRequestById, getEmptySavedObjectsResponse, -} from '../__mocks__/request_responses'; -import { requestContextMock, serverMock, requestMock } from '../__mocks__'; -import { deleteRulesBulkRoute } from './delete_rules_bulk_route'; +} from '../../../../routes/__mocks__/request_responses'; +import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; +import { bulkDeleteRulesRoute } from './route'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -describe('delete_rules', () => { +describe('Bulk delete rules route', () => { let server: ReturnType<typeof serverMock.create>; let { clients, context } = requestContextMock.createTools(); @@ -32,7 +32,7 @@ describe('delete_rules', () => { clients.rulesClient.delete.mockResolvedValue({}); // successful deletion clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse()); // rule status request - deleteRulesBulkRoute(server.router, logger); + bulkDeleteRulesRoute(server.router, logger); }); describe('status codes with actionClient and alertClient', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts similarity index 68% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts index 2a459dd7c2941..399b12ded105e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts @@ -5,32 +5,40 @@ * 2.0. */ +import type { RouteConfig, RequestHandler, Logger } from '@kbn/core/server'; import { validate } from '@kbn/securitysolution-io-ts-utils'; -import type { RouteConfig, RequestHandler, Logger } from '@kbn/core/server'; -import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import type { QueryRulesBulkSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/query_rules_bulk_schema'; -import { queryRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/query_rules_bulk_schema'; -import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; +import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../../../common/constants'; +import { + BulkDeleteRulesRequestBody, + validateQueryRuleByIds, + BulkCrudRulesResponse, +} from '../../../../../../../common/detection_engine/rule_management'; + +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; import type { SecuritySolutionPluginRouter, SecuritySolutionRequestHandlerContext, -} from '../../../../types'; -import { DETECTION_ENGINE_RULES_BULK_DELETE } from '../../../../../common/constants'; -import { getIdBulkError } from './utils'; -import { transformValidateBulkError } from './validate'; -import { transformBulkError, buildSiemResponse, createBulkErrorObject } from '../utils'; -import { deleteRules } from '../../rules/delete_rules'; -import { readRules } from '../../rules/read_rules'; -import { legacyMigrate } from '../../rules/utils'; -import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation'; +} from '../../../../../../types'; + +import { getIdBulkError } from '../../../utils/utils'; +import { transformValidateBulkError } from '../../../utils/validate'; +import { + transformBulkError, + buildSiemResponse, + createBulkErrorObject, +} from '../../../../routes/utils'; +import { deleteRules } from '../../../logic/crud/delete_rules'; +import { readRules } from '../../../logic/crud/read_rules'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; +import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../../deprecation'; -type Config = RouteConfig<unknown, unknown, QueryRulesBulkSchemaDecoded, 'delete' | 'post'>; +type Config = RouteConfig<unknown, unknown, BulkDeleteRulesRequestBody, 'delete' | 'post'>; type Handler = RequestHandler< unknown, unknown, - QueryRulesBulkSchemaDecoded, + BulkDeleteRulesRequestBody, SecuritySolutionRequestHandlerContext, 'delete' | 'post' >; @@ -38,12 +46,10 @@ type Handler = RequestHandler< /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const deleteRulesBulkRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { +export const bulkDeleteRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { const config: Config = { validate: { - body: buildRouteValidation<typeof queryRulesBulkSchema, QueryRulesBulkSchemaDecoded>( - queryRulesBulkSchema - ), + body: buildRouteValidation(BulkDeleteRulesRequestBody), }, path: DETECTION_ENGINE_RULES_BULK_DELETE, options: { @@ -65,7 +71,7 @@ export const deleteRulesBulkRoute = (router: SecuritySolutionPluginRouter, logge request.body.map(async (payloadRule) => { const { id, rule_id: ruleId } = payloadRule; const idOrRuleIdOrUnknown = id ?? ruleId ?? '(unknown id)'; - const validationErrors = queryRuleValidateTypeDependents(payloadRule); + const validationErrors = validateQueryRuleByIds(payloadRule); if (validationErrors.length) { return createBulkErrorObject({ ruleId: idOrRuleIdOrUnknown, @@ -103,7 +109,7 @@ export const deleteRulesBulkRoute = (router: SecuritySolutionPluginRouter, logge } }) ); - const [validated, errors] = validate(rules, rulesBulkSchema); + const [validated, errors] = validate(rules, BulkCrudRulesResponse); if (errors != null) { return siemResponse.error({ statusCode: 500, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts index da2808e572cf3..2082aa51815fd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.test.ts @@ -8,34 +8,35 @@ import { DETECTION_ENGINE_RULES_BULK_UPDATE, DETECTION_ENGINE_RULES_URL, -} from '../../../../../common/constants'; -import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; -import { buildMlAuthz } from '../../../machine_learning/authz'; +} from '../../../../../../../common/constants'; +import { mlServicesMock } from '../../../../../machine_learning/mocks'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { getEmptyFindResult, getFindResultWithSingleHit, getPatchBulkRequest, getRuleMock, typicalMlRulePayload, -} from '../__mocks__/request_responses'; -import { serverMock, requestContextMock, requestMock } from '../__mocks__'; -import { patchRulesBulkRoute } from './patch_rules_bulk_route'; -import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; -import { getMlRuleParams, getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +} from '../../../../routes/__mocks__/request_responses'; +import { serverMock, requestContextMock, requestMock } from '../../../../routes/__mocks__'; +import { bulkPatchRulesRoute } from './route'; +import { getCreateRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_schema/mocks'; +import { getMlRuleParams, getQueryRuleParams } from '../../../../rule_schema/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { legacyMigrate } from '../../rules/utils'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; -jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); +jest.mock('../../../../../machine_learning/authz'); -jest.mock('../../rules/utils', () => { - const actual = jest.requireActual('../../rules/utils'); +jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { + const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); return { ...actual, legacyMigrate: jest.fn(), }; }); -describe('patch_rules_bulk', () => { +describe('Bulk patch rules route', () => { let server: ReturnType<typeof serverMock.create>; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType<typeof mlServicesMock.createSetupContract>; @@ -51,7 +52,7 @@ describe('patch_rules_bulk', () => { (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - patchRulesBulkRoute(server.router, ml, logger); + bulkPatchRulesRoute(server.router, ml, logger); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts similarity index 73% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts index f80a5ffb94e5c..1bbba63daa846 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts @@ -7,26 +7,31 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { Logger } from '@kbn/core/server'; -import { patchRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/patch_rules_bulk_schema'; -import { buildRouteValidationNonExact } from '../../../../utils/build_validation/route_validation'; -import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../common/constants'; -import type { SetupPlugins } from '../../../../plugin'; -import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwAuthzError } from '../../../machine_learning/validation'; -import { transformBulkError, buildSiemResponse } from '../utils'; -import { getIdBulkError } from './utils'; -import { transformValidateBulkError } from './validate'; -import { patchRules } from '../../rules/patch_rules'; -import { readRules } from '../../rules/read_rules'; -import { legacyMigrate } from '../../rules/utils'; -import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation'; + +import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../../../common/constants'; +import { + BulkPatchRulesRequestBody, + BulkCrudRulesResponse, +} from '../../../../../../../common/detection_engine/rule_management'; + +import { buildRouteValidationNonExact } from '../../../../../../utils/build_validation/route_validation'; +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import type { SetupPlugins } from '../../../../../../plugin'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import { throwAuthzError } from '../../../../../machine_learning/validation'; +import { transformBulkError, buildSiemResponse } from '../../../../routes/utils'; +import { getIdBulkError } from '../../../utils/utils'; +import { transformValidateBulkError } from '../../../utils/validate'; +import { patchRules } from '../../../logic/crud/patch_rules'; +import { readRules } from '../../../logic/crud/read_rules'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; +import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../../deprecation'; /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const patchRulesBulkRoute = ( +export const bulkPatchRulesRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], logger: Logger @@ -35,7 +40,7 @@ export const patchRulesBulkRoute = ( { path: DETECTION_ENGINE_RULES_BULK_UPDATE, validate: { - body: buildRouteValidationNonExact<typeof patchRulesBulkSchema>(patchRulesBulkSchema), + body: buildRouteValidationNonExact(BulkPatchRulesRequestBody), }, options: { tags: ['access:securitySolution'], @@ -101,7 +106,7 @@ export const patchRulesBulkRoute = ( }) ); - const [validated, errors] = validate(rules, rulesBulkSchema); + const [validated, errors] = validate(rules, BulkCrudRulesResponse); if (errors != null) { return siemResponse.error({ statusCode: 500, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts similarity index 85% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts index 09e4dacec09de..5c8433b3e87fb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.test.ts @@ -5,35 +5,36 @@ * 2.0. */ -import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../common/constants'; -import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; -import { buildMlAuthz } from '../../../machine_learning/authz'; +import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../../../common/constants'; +import { mlServicesMock } from '../../../../../machine_learning/mocks'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { getEmptyFindResult, getRuleMock, getFindResultWithSingleHit, getUpdateBulkRequest, typicalMlRulePayload, -} from '../__mocks__/request_responses'; -import { serverMock, requestContextMock, requestMock } from '../__mocks__'; -import { updateRulesBulkRoute } from './update_rules_bulk_route'; -import type { BulkError } from '../utils'; -import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +} from '../../../../routes/__mocks__/request_responses'; +import { serverMock, requestContextMock, requestMock } from '../../../../routes/__mocks__'; +import { bulkUpdateRulesRoute } from './route'; +import type { BulkError } from '../../../../routes/utils'; +import { getCreateRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_schema/mocks'; +import { getQueryRuleParams } from '../../../../rule_schema/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { legacyMigrate } from '../../rules/utils'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; -jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); +jest.mock('../../../../../machine_learning/authz'); -jest.mock('../../rules/utils', () => { - const actual = jest.requireActual('../../rules/utils'); +jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { + const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); return { ...actual, legacyMigrate: jest.fn(), }; }); -describe('update_rules_bulk', () => { +describe('Bulk update rules route', () => { let server: ReturnType<typeof serverMock.create>; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType<typeof mlServicesMock.createSetupContract>; @@ -51,7 +52,7 @@ describe('update_rules_bulk', () => { (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - updateRulesBulkRoute(server.router, ml, logger); + bulkUpdateRulesRoute(server.router, ml, logger); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts similarity index 71% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts index d949a4bb9f5f9..5e22fc4d41c20 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts @@ -5,29 +5,38 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { Logger } from '@kbn/core/server'; -import { updateRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/update_rules_type_dependents'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { updateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/update_rules_bulk_schema'; -import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../common/constants'; -import type { SetupPlugins } from '../../../../plugin'; -import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwAuthzError } from '../../../machine_learning/validation'; -import { getIdBulkError } from './utils'; -import { transformValidateBulkError } from './validate'; -import { transformBulkError, buildSiemResponse, createBulkErrorObject } from '../utils'; -import { updateRules } from '../../rules/update_rules'; -import { legacyMigrate } from '../../rules/utils'; -import { readRules } from '../../rules/read_rules'; -import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation'; +import { validate } from '@kbn/securitysolution-io-ts-utils'; + +import { + BulkUpdateRulesRequestBody, + validateUpdateRuleProps, + BulkCrudRulesResponse, +} from '../../../../../../../common/detection_engine/rule_management'; + +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../../../common/constants'; +import type { SetupPlugins } from '../../../../../../plugin'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import { throwAuthzError } from '../../../../../machine_learning/validation'; +import { getIdBulkError } from '../../../utils/utils'; +import { transformValidateBulkError } from '../../../utils/validate'; +import { + transformBulkError, + buildSiemResponse, + createBulkErrorObject, +} from '../../../../routes/utils'; +import { updateRules } from '../../../logic/crud/update_rules'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; +import { readRules } from '../../../logic/crud/read_rules'; +import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from '../../deprecation'; /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead */ -export const updateRulesBulkRoute = ( +export const bulkUpdateRulesRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'], logger: Logger @@ -36,7 +45,7 @@ export const updateRulesBulkRoute = ( { path: DETECTION_ENGINE_RULES_BULK_UPDATE, validate: { - body: buildRouteValidation(updateRulesBulkSchema), + body: buildRouteValidation(BulkUpdateRulesRequestBody), }, options: { tags: ['access:securitySolution'], @@ -64,7 +73,7 @@ export const updateRulesBulkRoute = ( request.body.map(async (payloadRule) => { const idOrRuleIdOrUnknown = payloadRule.id ?? payloadRule.rule_id ?? '(unknown id)'; try { - const validationErrors = updateRuleValidateTypeDependents(payloadRule); + const validationErrors = validateUpdateRuleProps(payloadRule); if (validationErrors.length) { return createBulkErrorObject({ ruleId: payloadRule.rule_id, @@ -104,7 +113,7 @@ export const updateRulesBulkRoute = ( }) ); - const [validated, errors] = validate(rules, rulesBulkSchema); + const [validated, errors] = validate(rules, BulkCrudRulesResponse); if (errors != null) { return siemResponse.error({ statusCode: 500, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts index f3c4a698d83d2..5c333cf240e2d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import { getEmptyFindResult, getRuleMock, @@ -13,18 +13,18 @@ import { getFindResultWithSingleHit, createMlRuleRequest, getBasicEmptySearchResponse, -} from '../__mocks__/request_responses'; -import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; -import { buildMlAuthz } from '../../../machine_learning/authz'; -import { requestContextMock, serverMock, requestMock } from '../__mocks__'; -import { createRulesRoute } from './create_rules_route'; -import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; +} from '../../../../routes/__mocks__/request_responses'; +import { mlServicesMock } from '../../../../../machine_learning/mocks'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; +import { createRuleRoute } from './route'; +import { getCreateRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_schema/mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { getQueryRuleParams } from '../../../../rule_schema/mocks'; -jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); +jest.mock('../../../../../machine_learning/authz'); -describe('create_rules', () => { +describe('Create rule route', () => { let server: ReturnType<typeof serverMock.create>; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType<typeof mlServicesMock.createSetupContract>; @@ -40,7 +40,7 @@ describe('create_rules', () => { context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); - createRulesRoute(server.router, ml); + createRuleRoute(server.router, ml); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts similarity index 69% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts index 6e76de5aa420d..cfea8ec27a5a7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts @@ -6,22 +6,24 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import type { SetupPlugins } from '../../../../plugin'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwAuthzError } from '../../../machine_learning/validation'; -import { readRules } from '../../rules/read_rules'; -import { buildSiemResponse } from '../utils'; -import { createRulesSchema } from '../../../../../common/detection_engine/schemas/request'; -import { transformValidate } from './validate'; -import { createRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/create_rules_type_dependents'; -import { createRules } from '../../rules/create_rules'; -import { checkDefaultRuleExceptionListReferences } from './utils/check_for_default_rule_exception_list'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { validateCreateRuleProps } from '../../../../../../../common/detection_engine/rule_management'; +import { RuleCreateProps } from '../../../../../../../common/detection_engine/rule_schema'; -export const createRulesRoute = ( +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import type { SetupPlugins } from '../../../../../../plugin'; +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import { throwAuthzError } from '../../../../../machine_learning/validation'; +import { readRules } from '../../../logic/crud/read_rules'; +import { buildSiemResponse } from '../../../../routes/utils'; + +import { createRules } from '../../../logic/crud/create_rules'; +import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; +import { transformValidate } from '../../../utils/validate'; + +export const createRuleRoute = ( router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml'] ): void => { @@ -29,7 +31,7 @@ export const createRulesRoute = ( { path: DETECTION_ENGINE_RULES_URL, validate: { - body: buildRouteValidation(createRulesSchema), + body: buildRouteValidation(RuleCreateProps), }, options: { tags: ['access:securitySolution'], @@ -37,7 +39,7 @@ export const createRulesRoute = ( }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = createRuleValidateTypeDependents(request.body); + const validationErrors = validateCreateRuleProps(request.body); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.test.ts similarity index 83% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.test.ts index 85c324008856c..5f5e81a6c3960 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import { getEmptyFindResult, resolveRuleMock, @@ -14,21 +14,22 @@ import { getDeleteRequestById, getEmptySavedObjectsResponse, getRuleMock, -} from '../__mocks__/request_responses'; -import { requestContextMock, serverMock, requestMock } from '../__mocks__'; -import { deleteRulesRoute } from './delete_rules_route'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; -import { legacyMigrate } from '../../rules/utils'; +} from '../../../../routes/__mocks__/request_responses'; +import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; +import { deleteRuleRoute } from './route'; +import { getQueryRuleParams } from '../../../../rule_schema/mocks'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; -jest.mock('../../rules/utils', () => { - const actual = jest.requireActual('../../rules/utils'); +jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { + const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); return { ...actual, legacyMigrate: jest.fn(), }; }); -describe('delete_rules', () => { +describe('Delete rule route', () => { let server: ReturnType<typeof serverMock.create>; let { clients, context } = requestContextMock.createTools(); @@ -41,7 +42,7 @@ describe('delete_rules', () => { (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - deleteRulesRoute(server.router); + deleteRuleRoute(server.router); }); describe('status codes with actionClient and alertClient', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts similarity index 65% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts index 2ad26472b699a..f5dfb61cd32ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/delete_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts @@ -6,27 +6,29 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents'; -import type { QueryRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/query_rules_schema'; -import { queryRulesSchema } from '../../../../../common/detection_engine/schemas/request/query_rules_schema'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import { deleteRules } from '../../rules/delete_rules'; -import { getIdError, transform } from './utils'; -import { buildSiemResponse } from '../utils'; -import { readRules } from '../../rules/read_rules'; -import { legacyMigrate } from '../../rules/utils'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { + QueryRuleByIds, + validateQueryRuleByIds, +} from '../../../../../../../common/detection_engine/rule_management'; -export const deleteRulesRoute = (router: SecuritySolutionPluginRouter) => { +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import { buildSiemResponse } from '../../../../routes/utils'; + +import { deleteRules } from '../../../logic/crud/delete_rules'; +import { readRules } from '../../../logic/crud/read_rules'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; +import { getIdError, transform } from '../../../utils/utils'; + +export const deleteRuleRoute = (router: SecuritySolutionPluginRouter) => { router.delete( { path: DETECTION_ENGINE_RULES_URL, validate: { - query: buildRouteValidation<typeof queryRulesSchema, QueryRulesSchemaDecoded>( - queryRulesSchema - ), + query: buildRouteValidation(QueryRuleByIds), }, options: { tags: ['access:securitySolution'], @@ -34,7 +36,7 @@ export const deleteRulesRoute = (router: SecuritySolutionPluginRouter) => { }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = queryRuleValidateTypeDependents(request.query); + const validationErrors = validateQueryRuleByIds(request.query); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/export_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts similarity index 71% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/export_rules_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts index 145ab6217d2ab..ec96f38f5f86e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/export_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts @@ -7,22 +7,21 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import type { Logger } from '@kbn/core/server'; -import type { - ExportRulesQuerySchemaDecoded, - ExportRulesSchemaDecoded, -} from '../../../../../common/detection_engine/schemas/request/export_rules_schema'; + +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import type { ExportRulesRequestQueryDecoded } from '../../../../../../../common/detection_engine/rule_management'; import { - exportRulesQuerySchema, - exportRulesSchema, -} from '../../../../../common/detection_engine/schemas/request/export_rules_schema'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import type { ConfigType } from '../../../../config'; -import { getNonPackagedRulesCount } from '../../rules/get_existing_prepackaged_rules'; -import { getExportByObjectIds } from '../../rules/get_export_by_object_ids'; -import { getExportAll } from '../../rules/get_export_all'; -import { buildSiemResponse } from '../utils'; + ExportRulesRequestBody, + ExportRulesRequestQuery, +} from '../../../../../../../common/detection_engine/rule_management'; + +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import type { ConfigType } from '../../../../../../config'; +import { getNonPackagedRulesCount } 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'; export const exportRulesRoute = ( router: SecuritySolutionPluginRouter, @@ -33,12 +32,10 @@ export const exportRulesRoute = ( { path: `${DETECTION_ENGINE_RULES_URL}/_export`, validate: { - query: buildRouteValidation<typeof exportRulesQuerySchema, ExportRulesQuerySchemaDecoded>( - exportRulesQuerySchema - ), - body: buildRouteValidation<typeof exportRulesSchema, ExportRulesSchemaDecoded>( - exportRulesSchema + query: buildRouteValidation<typeof ExportRulesRequestQuery, ExportRulesRequestQueryDecoded>( + ExportRulesRequestQuery ), + body: buildRouteValidation(ExportRulesRequestBody), }, options: { tags: ['access:securitySolution'], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts similarity index 91% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts index b1f1ec8ead8ac..e34dff9733794 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.test.ts @@ -6,18 +6,18 @@ */ import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { DETECTION_ENGINE_RULES_URL_FIND } from '../../../../../common/constants'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; -import { requestContextMock, requestMock, serverMock } from '../__mocks__'; +import { DETECTION_ENGINE_RULES_URL_FIND } from '../../../../../../../common/constants'; +import { getQueryRuleParams } from '../../../../rule_schema/mocks'; +import { requestContextMock, requestMock, serverMock } from '../../../../routes/__mocks__'; import { getRuleMock, getFindRequest, getFindResultWithSingleHit, getEmptySavedObjectsResponse, -} from '../__mocks__/request_responses'; -import { findRulesRoute } from './find_rules_route'; +} from '../../../../routes/__mocks__/request_responses'; +import { findRulesRoute } from './route'; -describe('find_rules', () => { +describe('Find rules route', () => { let server: ReturnType<typeof serverMock.create>; let { clients, context } = requestContextMock.createTools(); let logger: ReturnType<typeof loggingSystemMock.createLogger>; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts similarity index 70% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts index fa28a16e7bd4b..34117ca07f817 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/find_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts @@ -5,28 +5,32 @@ * 2.0. */ -import { transformError } from '@kbn/securitysolution-es-utils'; import type { Logger } from '@kbn/core/server'; -import { findRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/find_rules_type_dependents'; -import type { FindRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/find_rules_schema'; -import { findRulesSchema } from '../../../../../common/detection_engine/schemas/request/find_rules_schema'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL_FIND } from '../../../../../common/constants'; -import { findRules } from '../../rules/find_rules'; -import { buildSiemResponse } from '../utils'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { transformFindAlerts } from './utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; + +import { DETECTION_ENGINE_RULES_URL_FIND } from '../../../../../../../common/constants'; +import type { FindRulesRequestQueryDecoded } from '../../../../../../../common/detection_engine/rule_management'; +import { + FindRulesRequestQuery, + validateFindRulesRequestQuery, +} from '../../../../../../../common/detection_engine/rule_management'; + +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import { findRules } from '../../../logic/search/find_rules'; +import { buildSiemResponse } from '../../../../routes/utils'; +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import { transformFindAlerts } from '../../../utils/utils'; // eslint-disable-next-line no-restricted-imports -import { legacyGetBulkRuleActionsSavedObject } from '../../rule_actions/legacy_get_bulk_rule_actions_saved_object'; +import { legacyGetBulkRuleActionsSavedObject } from '../../../../rule_actions_legacy'; export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { router.get( { path: DETECTION_ENGINE_RULES_URL_FIND, validate: { - query: buildRouteValidation<typeof findRulesSchema, FindRulesSchemaDecoded>( - findRulesSchema + query: buildRouteValidation<typeof FindRulesRequestQuery, FindRulesRequestQueryDecoded>( + FindRulesRequestQuery ), }, options: { @@ -36,7 +40,7 @@ export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Log async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = findRuleValidateTypeDependents(request.query); + const validationErrors = validateFindRulesRequestQuery(request.query); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts similarity index 95% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts index bf83471631a8c..3d466d2505074 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.test.ts @@ -5,7 +5,20 @@ * 2.0. */ -import { buildHapiStream } from '../__mocks__/utils'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; + +import { + getImportRulesWithIdSchemaMock, + ruleIdsToNdJsonString, + rulesToNdJsonString, +} from '../../../../../../../common/detection_engine/rule_management/mocks'; + +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import { mlServicesMock } from '../../../../../machine_learning/mocks'; + +import type { requestMock } from '../../../../routes/__mocks__'; +import { createMockConfig, requestContextMock, serverMock } from '../../../../routes/__mocks__'; +import { buildHapiStream } from '../../../../routes/__mocks__/utils'; import { getImportRulesRequest, getImportRulesRequestOverwriteTrue, @@ -13,24 +26,15 @@ import { getRuleMock, getFindResultWithSingleHit, getBasicEmptySearchResponse, -} from '../__mocks__/request_responses'; -import type { requestMock } from '../__mocks__'; -import { createMockConfig, requestContextMock, serverMock } from '../__mocks__'; -import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; -import { buildMlAuthz } from '../../../machine_learning/authz'; -import { importRulesRoute } from './import_rules_route'; -import * as createRulesAndExceptionsStreamFromNdJson from '../../rules/create_rules_stream_from_ndjson'; -import { - getImportRulesWithIdSchemaMock, - ruleIdsToNdJsonString, - rulesToNdJsonString, -} from '../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; -import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +} from '../../../../routes/__mocks__/request_responses'; + +import * as createRulesAndExceptionsStreamFromNdJson from '../../../logic/import/create_rules_stream_from_ndjson'; +import { getQueryRuleParams } from '../../../../rule_schema/mocks'; +import { importRulesRoute } from './route'; -jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); +jest.mock('../../../../../machine_learning/authz'); -describe('import_rules_route', () => { +describe('Import rules route', () => { let config: ReturnType<typeof createMockConfig>; let server: ReturnType<typeof serverMock.create>; let request: ReturnType<typeof requestMock.create>; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts similarity index 79% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts index 958993b34c03b..e306caac12194 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts @@ -15,29 +15,29 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import type { ImportQuerySchemaDecoded } from '@kbn/securitysolution-io-ts-types'; import { importQuerySchema } from '@kbn/securitysolution-io-ts-types'; -import type { ImportRulesSchema as ImportRulesResponseSchema } from '../../../../../common/detection_engine/schemas/response/import_rules_schema'; -import { importRulesSchema as importRulesResponseSchema } from '../../../../../common/detection_engine/schemas/response/import_rules_schema'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import type { ConfigType } from '../../../../config'; -import type { SetupPlugins } from '../../../../plugin'; -import { buildMlAuthz } from '../../../machine_learning/authz'; -import type { ImportRuleResponse, BulkError } from '../utils'; -import { isBulkError, isImportRegular, buildSiemResponse } from '../utils'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import type { RuleToImport } from '../../../../../../../common/detection_engine/rule_management'; +import { ImportRulesResponse } from '../../../../../../../common/detection_engine/rule_management'; + +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import type { ConfigType } from '../../../../../../config'; +import type { SetupPlugins } from '../../../../../../plugin'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import type { ImportRuleResponse, BulkError } from '../../../../routes/utils'; +import { isBulkError, isImportRegular, buildSiemResponse } from '../../../../routes/utils'; import { getTupleDuplicateErrorsAndUniqueRules, getInvalidConnectors, migrateLegacyActionsIds, -} from './utils'; -import { createRulesAndExceptionsStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import type { HapiReadableStream } from '../../rules/types'; -import type { RuleExceptionsPromiseFromStreams } from './utils/import_rules_utils'; -import { importRules as importRulesHelper } from './utils/import_rules_utils'; -import { getReferencedExceptionLists } from './utils/gather_referenced_exceptions'; -import { importRuleExceptions } from './utils/import_rule_exceptions'; -import type { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; +} from '../../../utils/utils'; +import { createRulesAndExceptionsStreamFromNdJson } from '../../../logic/import/create_rules_stream_from_ndjson'; +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import type { RuleExceptionsPromiseFromStreams } from '../../../logic/import/import_rules_utils'; +import { importRules as importRulesHelper } from '../../../logic/import/import_rules_utils'; +import { getReferencedExceptionLists } from '../../../logic/import/gather_referenced_exceptions'; +import { importRuleExceptions } from '../../../logic/import/import_rule_exceptions'; +import type { HapiReadableStream } from '../../../logic/import/hapi_readable_stream'; const CHUNK_PARSED_OBJECT_SIZE = 50; @@ -131,9 +131,7 @@ export const importRulesRoute = ( let parsedRules; let actionErrors: BulkError[] = []; - const actualRules = rules.filter( - (rule): rule is ImportRulesSchema => !(rule instanceof Error) - ); + const actualRules = rules.filter((rule): rule is RuleToImport => !(rule instanceof Error)); if (actualRules.some((rule) => rule.actions && rule.actions.length > 0)) { const [nonExistentActionErrors, uniqueParsedObjects] = await getInvalidConnectors( @@ -173,7 +171,7 @@ export const importRulesRoute = ( return false; } }); - const importRules: ImportRulesResponseSchema = { + const importRules: ImportRulesResponse = { success: errorsResp.length === 0, success_count: successes.length, rules_count: rules.length, @@ -183,7 +181,7 @@ export const importRulesRoute = ( exceptions_success_count: exceptionsSuccessCount, }; - const [validated, errors] = validate(importRules, importRulesResponseSchema); + const [validated, errors] = validate(importRules, ImportRulesResponse); if (errors != null) { return siemResponse.error({ statusCode: 500, body: errors }); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts index 743fdefa7947f..07ae6a84df003 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.test.ts @@ -5,9 +5,13 @@ * 2.0. */ -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; -import { buildMlAuthz } from '../../../machine_learning/authz'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { getPatchRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_management/mocks'; + +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import { mlServicesMock } from '../../../../../machine_learning/mocks'; + +import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; import { getEmptyFindResult, getRuleMock, @@ -15,24 +19,25 @@ import { getFindResultWithSingleHit, nonRuleFindResult, typicalMlRulePayload, -} from '../__mocks__/request_responses'; -import { requestContextMock, serverMock, requestMock } from '../__mocks__'; -import { patchRulesRoute } from './patch_rules_route'; -import { getPatchRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/patch_rules_schema.mock'; -import { getMlRuleParams, getQueryRuleParams } from '../../schemas/rule_schemas.mock'; -import { legacyMigrate } from '../../rules/utils'; +} from '../../../../routes/__mocks__/request_responses'; + +import { getMlRuleParams, getQueryRuleParams } from '../../../../rule_schema/mocks'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; + +import { patchRuleRoute } from './route'; -jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); +jest.mock('../../../../../machine_learning/authz'); -jest.mock('../../rules/utils', () => { - const actual = jest.requireActual('../../rules/utils'); +jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { + const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); return { ...actual, legacyMigrate: jest.fn(), }; }); -describe('patch_rules', () => { +describe('Patch rule route', () => { let server: ReturnType<typeof serverMock.create>; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType<typeof mlServicesMock.createSetupContract>; @@ -48,7 +53,7 @@ describe('patch_rules', () => { (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - patchRulesRoute(server.router, ml); + patchRuleRoute(server.router, ml); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts similarity index 69% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts index aac618529c3bb..0e15e73b5e34d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts @@ -6,23 +6,29 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { patchRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/patch_rules_type_dependents'; -import { buildRouteValidationNonExact } from '../../../../utils/build_validation/route_validation'; -import { patchRulesSchema } from '../../../../../common/detection_engine/schemas/request/patch_rules_schema'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import type { SetupPlugins } from '../../../../plugin'; -import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwAuthzError } from '../../../machine_learning/validation'; -import { patchRules } from '../../rules/patch_rules'; -import { buildSiemResponse } from '../utils'; -import { checkDefaultRuleExceptionListReferences } from './utils/check_for_default_rule_exception_list'; -import { getIdError } from './utils'; -import { transformValidate } from './validate'; -import { readRules } from '../../rules/read_rules'; -import { legacyMigrate } from '../../rules/utils'; -export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { + PatchRuleRequestBody, + validatePatchRuleRequestBody, +} from '../../../../../../../common/detection_engine/rule_management'; + +import { buildRouteValidationNonExact } from '../../../../../../utils/build_validation/route_validation'; +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import type { SetupPlugins } from '../../../../../../plugin'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import { throwAuthzError } from '../../../../../machine_learning/validation'; +import { buildSiemResponse } from '../../../../routes/utils'; + +import { readRules } from '../../../logic/crud/read_rules'; +import { patchRules } from '../../../logic/crud/patch_rules'; +import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; +import { getIdError } from '../../../utils/utils'; +import { transformValidate } from '../../../utils/validate'; + +export const patchRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.patch( { path: DETECTION_ENGINE_RULES_URL, @@ -30,7 +36,7 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP // Use non-exact validation because everything is optional in patch - since everything is optional, // io-ts can't find the right schema from the type specific union and the exact check breaks. // We do type specific validation after fetching the existing rule so we know the rule type. - body: buildRouteValidationNonExact<typeof patchRulesSchema>(patchRulesSchema), + body: buildRouteValidationNonExact(PatchRuleRequestBody), }, options: { tags: ['access:securitySolution'], @@ -38,7 +44,7 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = patchRuleValidateTypeDependents(request.body); + const validationErrors = validatePatchRuleRequestBody(request.body); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.test.ts similarity index 92% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.test.ts index 368a2b50cd962..573a85cc97b16 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.test.ts @@ -7,8 +7,8 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import { readRulesRoute } from './read_rules_route'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { readRuleRoute } from './route'; import { getEmptyFindResult, getReadRequest, @@ -17,11 +17,11 @@ import { nonRuleFindResult, getEmptySavedObjectsResponse, resolveRuleMock, -} from '../__mocks__/request_responses'; -import { requestMock, requestContextMock, serverMock } from '../__mocks__'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +} from '../../../../routes/__mocks__/request_responses'; +import { requestMock, requestContextMock, serverMock } from '../../../../routes/__mocks__'; +import { getQueryRuleParams } from '../../../../rule_schema/mocks'; -describe('read_rules', () => { +describe('Read rule route', () => { let server: ReturnType<typeof serverMock.create>; let { clients, context } = requestContextMock.createTools(); let logger: ReturnType<typeof loggingSystemMock.createLogger>; @@ -41,7 +41,7 @@ describe('read_rules', () => { }), id: myFakeId, }); - readRulesRoute(server.router, logger); + readRuleRoute(server.router, logger); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts similarity index 65% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts index b19e94b6f0484..eaa58bf00a1be 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/read_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts @@ -5,29 +5,30 @@ * 2.0. */ -import { transformError } from '@kbn/securitysolution-es-utils'; import type { Logger } from '@kbn/core/server'; -import { queryRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/query_rules_type_dependents'; -import type { QueryRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/query_rules_schema'; -import { queryRulesSchema } from '../../../../../common/detection_engine/schemas/request/query_rules_schema'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import { getIdError, transform } from './utils'; -import { buildSiemResponse } from '../utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; + +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { + QueryRuleByIds, + validateQueryRuleByIds, +} from '../../../../../../../common/detection_engine/rule_management'; + +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +import { buildSiemResponse } from '../../../../routes/utils'; +import { getIdError, transform } from '../../../utils/utils'; -import { readRules } from '../../rules/read_rules'; +import { readRules } from '../../../logic/crud/read_rules'; // eslint-disable-next-line no-restricted-imports -import { legacyGetRuleActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; +import { legacyGetRuleActionsSavedObject } from '../../../../rule_actions_legacy'; -export const readRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { +export const readRuleRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { router.get( { path: DETECTION_ENGINE_RULES_URL, validate: { - query: buildRouteValidation<typeof queryRulesSchema, QueryRulesSchemaDecoded>( - queryRulesSchema - ), + query: buildRouteValidation(QueryRuleByIds), }, options: { tags: ['access:securitySolution'], @@ -35,7 +36,7 @@ export const readRulesRoute = (router: SecuritySolutionPluginRouter, logger: Log }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = queryRuleValidateTypeDependents(request.query); + const validationErrors = validateQueryRuleByIds(request.query); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts similarity index 86% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts index ad7a7d1fe5365..4502e08fb950d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { mlServicesMock, mlAuthzMock as mockMlAuthzFactory } from '../../../machine_learning/mocks'; -import { buildMlAuthz } from '../../../machine_learning/authz'; +import { mlServicesMock } from '../../../../../machine_learning/mocks'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; import { getEmptyFindResult, getRuleMock, @@ -14,25 +14,26 @@ import { getFindResultWithSingleHit, nonRuleFindResult, typicalMlRulePayload, -} from '../__mocks__/request_responses'; -import { requestContextMock, serverMock, requestMock } from '../__mocks__'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import { updateRulesRoute } from './update_rules_route'; -import { getUpdateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; -import { legacyMigrate } from '../../rules/utils'; - -jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); - -jest.mock('../../rules/utils', () => { - const actual = jest.requireActual('../../rules/utils'); +} from '../../../../routes/__mocks__/request_responses'; +import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { updateRuleRoute } from './route'; +import { getUpdateRulesSchemaMock } from '../../../../../../../common/detection_engine/rule_schema/mocks'; +import { getQueryRuleParams } from '../../../../rule_schema/mocks'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; + +jest.mock('../../../../../machine_learning/authz'); + +jest.mock('../../../logic/rule_actions/legacy_action_migration', () => { + const actual = jest.requireActual('../../../logic/rule_actions/legacy_action_migration'); return { ...actual, legacyMigrate: jest.fn(), }; }); -describe('update_rules', () => { +describe('Update rule route', () => { let server: ReturnType<typeof serverMock.create>; let { clients, context } = requestContextMock.createTools(); let ml: ReturnType<typeof mlServicesMock.createSetupContract>; @@ -49,7 +50,7 @@ describe('update_rules', () => { (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getQueryRuleParams())); - updateRulesRoute(server.router, ml); + updateRuleRoute(server.router, ml); }); describe('status codes', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts similarity index 64% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts index 8fa4f372fd597..8d24dd03d9d01 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts @@ -6,29 +6,31 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { updateRulesSchema } from '../../../../../common/detection_engine/schemas/request'; -import { updateRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/update_rules_type_dependents'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import type { SetupPlugins } from '../../../../plugin'; -import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwAuthzError } from '../../../machine_learning/validation'; -import { buildSiemResponse } from '../utils'; -import { getIdError } from './utils'; -import { transformValidate } from './validate'; -import { updateRules } from '../../rules/update_rules'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { legacyMigrate } from '../../rules/utils'; -import { readRules } from '../../rules/read_rules'; -import { checkDefaultRuleExceptionListReferences } from './utils/check_for_default_rule_exception_list'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; +import { validateUpdateRuleProps } from '../../../../../../../common/detection_engine/rule_management'; +import { RuleUpdateProps } from '../../../../../../../common/detection_engine/rule_schema'; +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import type { SetupPlugins } from '../../../../../../plugin'; +import { buildMlAuthz } from '../../../../../machine_learning/authz'; +import { throwAuthzError } from '../../../../../machine_learning/validation'; +import { buildSiemResponse } from '../../../../routes/utils'; -export const updateRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { +import { getIdError } from '../../../utils/utils'; +import { transformValidate } from '../../../utils/validate'; +import { updateRules } from '../../../logic/crud/update_rules'; +import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../../../logic/rule_actions/legacy_action_migration'; +import { readRules } from '../../../logic/crud/read_rules'; +import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; + +export const updateRuleRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.put( { path: DETECTION_ENGINE_RULES_URL, validate: { - body: buildRouteValidation(updateRulesSchema), + body: buildRouteValidation(RuleUpdateProps), }, options: { tags: ['access:securitySolution'], @@ -36,7 +38,7 @@ export const updateRulesRoute = (router: SecuritySolutionPluginRouter, ml: Setup }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = updateRuleValidateTypeDependents(request.body); + const validationErrors = validateUpdateRuleProps(request.body); if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/read_tags.test.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/read_tags.test.ts index 2154e672e0089..ba9b00c01e3cf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/read_tags.test.ts @@ -6,9 +6,12 @@ */ import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; -import { getRuleMock, getFindResultWithMultiHits } from '../routes/__mocks__/request_responses'; +import { + getRuleMock, + getFindResultWithMultiHits, +} from '../../../../routes/__mocks__/request_responses'; +import { getQueryRuleParams } from '../../../../rule_schema/mocks'; import { readTags, convertTagsToSet, convertToTags, isTags } from './read_tags'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; describe('read_tags', () => { afterEach(() => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/read_tags.ts similarity index 96% rename from x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/read_tags.ts index 72a886cd3e3a1..8819d95f366d8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/tags/read_tags.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/read_tags.ts @@ -7,7 +7,7 @@ import { has } from 'lodash/fp'; import type { RulesClient } from '@kbn/alerting-plugin/server'; -import { findRules } from '../rules/find_rules'; +import { findRules } from '../../../logic/search/find_rules'; export interface TagType { id: string; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags/read_tags_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/route.ts similarity index 81% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags/read_tags_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/route.ts index 508495cdeb9ef..c7cca7e443bd3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/tags/read_tags_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/route.ts @@ -6,11 +6,11 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { DETECTION_ENGINE_TAGS_URL } from '../../../../../common/constants'; -import { buildSiemResponse } from '../utils'; +import type { SecuritySolutionPluginRouter } from '../../../../../../types'; +import { DETECTION_ENGINE_TAGS_URL } from '../../../../../../../common/constants'; +import { buildSiemResponse } from '../../../../routes/utils'; -import { readTags } from '../../tags/read_tags'; +import { readTags } from './read_tags'; export const readTagsRoute = (router: SecuritySolutionPluginRouter) => { router.get( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/index.ts new file mode 100644 index 0000000000000..f2a694c8df046 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/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 * from './api/register_routes'; + +// TODO: https://github.com/elastic/kibana/pull/142950 +// eslint-disable-next-line no-restricted-imports +export { legacyMigrate } from './logic/rule_actions/legacy_action_migration'; + +// TODO: https://github.com/elastic/kibana/pull/142950 +// TODO: Revisit and consider moving to the rule_schema subdomain +export { + commonParamsCamelToSnake, + typeSpecificCamelToSnake, + convertCreateAPIToInternalSchema, +} from './normalization/rule_converters'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts similarity index 99% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts index 29cc718379c96..8637236f654d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts @@ -7,7 +7,7 @@ import uuid from 'uuid'; import type { SanitizedRule } from '@kbn/alerting-plugin/common'; -import type { RuleParams } from '../schemas/rule_schemas'; +import type { RuleParams } from '../../../rule_schema'; import { duplicateRule } from './duplicate_rule'; jest.mock('uuid', () => ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.ts similarity index 91% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.ts index de077ef91b168..5f15a2ed81d1f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/duplicate_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.ts @@ -6,13 +6,12 @@ */ import uuid from 'uuid'; - import { i18n } from '@kbn/i18n'; import { ruleTypeMappings } from '@kbn/securitysolution-rules'; - import type { SanitizedRule } from '@kbn/alerting-plugin/common'; -import { SERVER_APP_ID } from '../../../../common/constants'; -import type { InternalRuleCreate, RuleParams } from '../schemas/rule_schemas'; + +import { SERVER_APP_ID } from '../../../../../../common/constants'; +import type { InternalRuleCreate, RuleParams } from '../../../rule_schema'; const DUPLICATE_TITLE = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.cloneRule.duplicateTitle', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/action_to_rules_client_operation.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.test.ts similarity index 91% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/action_to_rules_client_operation.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.test.ts index 8627a5efeca2e..e48f0b4cbd3c4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/action_to_rules_client_operation.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.test.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { BulkActionEditType } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; - +import { BulkActionEditType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { bulkEditActionToRulesClientOperation } from './action_to_rules_client_operation'; describe('bulkEditActionToRulesClientOperation', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/action_to_rules_client_operation.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/action_to_rules_client_operation.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.ts index 550f624ed9351..f767a5c246056 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/action_to_rules_client_operation.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/action_to_rules_client_operation.ts @@ -7,11 +7,11 @@ import type { BulkEditOperation } from '@kbn/alerting-plugin/server'; -import type { BulkActionEditForRuleAttributes } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { BulkActionEditType } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { assertUnreachable } from '../../../../../common/utility_types'; +import type { BulkActionEditForRuleAttributes } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionEditType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { assertUnreachable } from '../../../../../../common/utility_types'; -import { transformToAlertThrottle, transformToNotifyWhen } from '../utils'; +import { transformToAlertThrottle, transformToNotifyWhen } from '../../normalization/rule_actions'; const getThrottleOperation = (throttle: string) => ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_edit_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts similarity index 80% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_edit_rules.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts index 75c7db48333c3..85610fcddb436 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_edit_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/bulk_edit_rules.ts @@ -7,24 +7,28 @@ import type { BulkEditError, RulesClient } from '@kbn/alerting-plugin/server'; import pMap from 'p-map'; -import type { - BulkActionEditPayload, - BulkActionEditPayloadRuleActions, -} from '../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { BulkActionEditType } from '../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { enrichFilterWithRuleTypeMapping } from './enrich_filter_with_rule_type_mappings'; -import type { MlAuthz } from '../../machine_learning/authz'; -import { ruleParamsModifier } from './bulk_actions/rule_params_modifier'; -import { splitBulkEditActions } from './bulk_actions/split_bulk_edit_actions'; -import { validateBulkEditRule } from './bulk_actions/validations'; -import { bulkEditActionToRulesClientOperation } from './bulk_actions/action_to_rules_client_operation'; -import type { RuleAlertType } from './types'; import { MAX_RULES_TO_UPDATE_IN_PARALLEL, NOTIFICATION_THROTTLE_NO_ACTIONS, -} from '../../../../common/constants'; -import { readRules } from './read_rules'; +} from '../../../../../../common/constants'; + +import type { + BulkActionEditPayload, + BulkActionEditPayloadRuleActions, +} from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionEditType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; + +import type { MlAuthz } from '../../../../machine_learning/authz'; + +import { enrichFilterWithRuleTypeMapping } from '../search/enrich_filter_with_rule_type_mappings'; +import { readRules } from '../crud/read_rules'; +import type { RuleAlertType } from '../../../rule_schema'; + +import { ruleParamsModifier } from './rule_params_modifier'; +import { splitBulkEditActions } from './split_bulk_edit_actions'; +import { validateBulkEditRule } from './validations'; +import { bulkEditActionToRulesClientOperation } from './action_to_rules_client_operation'; export interface BulkEditRulesArguments { rulesClient: RulesClient; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/dry_run.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/dry_run.ts similarity index 93% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/dry_run.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/dry_run.ts index 608324e5bb280..fa05b1fc822d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/dry_run.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/dry_run.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { BulkActionsDryRunErrCode } from '../../../../../common/constants'; +import type { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; /** * Error instance that has properties: errorCode & statusCode to use within run_dry diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.test.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.test.ts index 5c194cc6c4614..b7763f20eec9c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.test.ts @@ -6,8 +6,8 @@ */ import { addItemsToArray, deleteItemsFromArray, ruleParamsModifier } from './rule_params_modifier'; -import { BulkActionEditType } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import type { RuleAlertType } from '../types'; +import { BulkActionEditType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import type { RuleAlertType } from '../../../rule_schema'; describe('addItemsToArray', () => { test('should add single item to array', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts similarity index 92% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts index 6831668258554..9f5f252b9fd86 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/rule_params_modifier.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts @@ -9,10 +9,10 @@ import moment from 'moment'; import { parseInterval } from '@kbn/data-plugin/common/search/aggs/utils/date_interval_utils'; -import type { RuleAlertType } from '../types'; -import type { BulkActionEditForRuleParams } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { BulkActionEditType } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { invariant } from '../../../../../common/utils/invariant'; +import type { RuleAlertType } from '../../../rule_schema'; +import type { BulkActionEditForRuleParams } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionEditType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { invariant } from '../../../../../../common/utils/invariant'; export const addItemsToArray = <T>(arr: T[], items: T[]): T[] => Array.from(new Set([...arr, ...items])); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/split_bulk_edit_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.test.ts similarity index 87% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/split_bulk_edit_actions.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.test.ts index 46205c060be78..33f1e0e4ad15e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/split_bulk_edit_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.test.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { BulkActionEditPayload } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { BulkActionEditType } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import type { BulkActionEditPayload } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionEditType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { splitBulkEditActions } from './split_bulk_edit_actions'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/split_bulk_edit_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.ts similarity index 85% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/split_bulk_edit_actions.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.ts index c752ca6653ad6..b6441d46c5ebd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/split_bulk_edit_actions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/split_bulk_edit_actions.ts @@ -5,12 +5,12 @@ * 2.0. */ -import { BulkActionEditType } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import { BulkActionEditType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import type { BulkActionEditPayload, BulkActionEditForRuleAttributes, BulkActionEditForRuleParams, -} from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +} from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; /** * Split bulk edit actions in 2 chunks: actions applied to params and diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/utils.ts similarity index 84% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/utils.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/utils.ts index 91cfe9544d550..993750cfc0964 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/utils.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { BulkActionEditType } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; +import { BulkActionEditType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; /** * helper utility that defines whether bulk edit action is related to index patterns, i.e. one of: diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/validations.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/validations.ts similarity index 84% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/validations.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/validations.ts index 5252fd1982ff3..ce9b084679a5b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/bulk_actions/validations.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/validations.ts @@ -6,16 +6,16 @@ */ import type { Type as RuleType } from '@kbn/securitysolution-io-ts-alerting-types'; -import { invariant } from '../../../../../common/utils/invariant'; -import { isMlRule } from '../../../../../common/machine_learning/helpers'; -import { BulkActionsDryRunErrCode } from '../../../../../common/constants'; -import type { BulkActionEditPayload } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import { BulkActionEditType } from '../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema'; -import type { RuleAlertType } from '../types'; +import { invariant } from '../../../../../../common/utils/invariant'; +import { isMlRule } from '../../../../../../common/machine_learning/helpers'; +import { BulkActionsDryRunErrCode } from '../../../../../../common/constants'; +import type { BulkActionEditPayload } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import { BulkActionEditType } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; +import type { RuleAlertType } from '../../../rule_schema'; import { isIndexPatternsBulkEditAction } from './utils'; import { throwDryRunError } from './dry_run'; -import type { MlAuthz } from '../../../machine_learning/authz'; -import { throwAuthzError } from '../../../machine_learning/validation'; +import type { MlAuthz } from '../../../../machine_learning/authz'; +import { throwAuthzError } from '../../../../machine_learning/validation'; interface BulkActionsValidationArgs { rule: RuleAlertType; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/create_rules.test.ts similarity index 94% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/create_rules.test.ts index fb62434563183..8b59effd9c910 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/create_rules.test.ts @@ -7,11 +7,11 @@ import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; import { createRules } from './create_rules'; -import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../common/constants'; +import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../../common/constants'; import { getCreateMachineLearningRulesSchemaMock, getCreateThreatMatchRulesSchemaMock, -} from '../../../../common/detection_engine/schemas/request/rule_schemas.mock'; +} from '../../../../../../common/detection_engine/rule_schema/mocks'; describe('createRules', () => { it('calls the rulesClient with legacy ML params', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/create_rules.ts similarity index 60% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/create_rules.ts index 5405fed52b79b..f621542fe433f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/create_rules.ts @@ -6,10 +6,20 @@ */ import type { SanitizedRule } from '@kbn/alerting-plugin/common'; -import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../common/constants'; -import type { CreateRulesOptions } from './types'; -import { convertCreateAPIToInternalSchema } from '../schemas/rule_converters'; -import type { RuleParams } from '../schemas/rule_schemas'; +import type { RulesClient } from '@kbn/alerting-plugin/server'; + +import type { RuleCreateProps } from '../../../../../../common/detection_engine/rule_schema'; +import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../../common/constants'; +import { convertCreateAPIToInternalSchema } from '../../normalization/rule_converters'; +import type { RuleParams } from '../../../rule_schema'; + +export interface CreateRulesOptions<T extends RuleCreateProps = RuleCreateProps> { + rulesClient: RulesClient; + params: T; + id?: string; + immutable?: boolean; + defaultEnabled?: boolean; +} export const createRules = async ({ rulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/delete_rules.test.ts similarity index 89% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/delete_rules.test.ts index 0c7edae7022c7..e7176b8d51af0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/delete_rules.test.ts @@ -6,9 +6,9 @@ */ import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; -import { ruleExecutionLogMock } from '../rule_monitoring/mocks'; +import { ruleExecutionLogMock } from '../../../rule_monitoring/mocks'; +import type { DeleteRuleOptions } from './delete_rules'; import { deleteRules } from './delete_rules'; -import type { DeleteRuleOptions } from './types'; describe('deleteRules', () => { let rulesClient: ReturnType<typeof rulesClientMock.create>; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/delete_rules.ts similarity index 55% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/delete_rules.ts index e0ab62df17c37..a1190d8827c0d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/delete_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/delete_rules.ts @@ -5,7 +5,15 @@ * 2.0. */ -import type { DeleteRuleOptions } from './types'; +import type { RulesClient } from '@kbn/alerting-plugin/server'; +import type { RuleObjectId } from '../../../../../../common/detection_engine/rule_schema'; +import type { IRuleExecutionLogForRoutes } from '../../../rule_monitoring'; + +export interface DeleteRuleOptions { + ruleId: RuleObjectId; + rulesClient: RulesClient; + ruleExecutionLog: IRuleExecutionLogForRoutes; +} export const deleteRules = async ({ ruleId, rulesClient, ruleExecutionLog }: DeleteRuleOptions) => { await rulesClient.delete({ id: ruleId }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/patch_rules.test.ts similarity index 96% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/patch_rules.test.ts index e99f7f85df156..cfb051273255a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/patch_rules.test.ts @@ -8,12 +8,12 @@ import { rulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock'; import { patchRules } from './patch_rules'; -import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock'; +import { getRuleMock } from '../../../routes/__mocks__/request_responses'; +import { getMlRuleParams, getQueryRuleParams } from '../../../rule_schema/mocks'; import { getCreateMachineLearningRulesSchemaMock, getCreateRulesSchemaMock, -} from '../../../../common/detection_engine/schemas/request/rule_schemas.mock'; +} from '../../../../../../common/detection_engine/rule_schema/mocks'; describe('patchRules', () => { it('should call rulesClient.disable if the rule was enabled and enabled is false', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/patch_rules.ts similarity index 70% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/patch_rules.ts index 0a8342ae265a0..656366ec9f92b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/patch_rules.ts @@ -5,11 +5,18 @@ * 2.0. */ -import type { PartialRule } from '@kbn/alerting-plugin/server'; -import type { RuleParams } from '../schemas/rule_schemas'; -import type { PatchRulesOptions } from './types'; -import { maybeMute } from './utils'; -import { convertPatchAPIToInternalSchema } from '../schemas/rule_converters'; +import type { PartialRule, RulesClient } from '@kbn/alerting-plugin/server'; + +import type { PatchRuleRequestBody } from '../../../../../../common/detection_engine/rule_management'; +import type { RuleAlertType, RuleParams } from '../../../rule_schema'; +import { convertPatchAPIToInternalSchema } from '../../normalization/rule_converters'; +import { maybeMute } from '../rule_actions/muting'; + +export interface PatchRulesOptions { + rulesClient: RulesClient; + nextParams: PatchRuleRequestBody; + existingRule: RuleAlertType | null | undefined; +} export const patchRules = async ({ rulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/read_rules.test.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/read_rules.test.ts index dd0d1ff7090c7..fa6ccba511833 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/read_rules.test.ts @@ -11,8 +11,8 @@ import { resolveRuleMock, getRuleMock, getFindResultWithSingleHit, -} from '../routes/__mocks__/request_responses'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; +} from '../../../routes/__mocks__/request_responses'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; export class TestError extends Error { constructor() { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/read_rules.ts similarity index 80% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/read_rules.ts index 1e853084a3635..76969b31aab66 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/read_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/read_rules.ts @@ -6,11 +6,22 @@ */ import type { ResolvedSanitizedRule, SanitizedRule } from '@kbn/alerting-plugin/common'; -import { withSecuritySpan } from '../../../utils/with_security_span'; -import type { RuleParams } from '../schemas/rule_schemas'; -import { findRules } from './find_rules'; -import type { ReadRuleOptions } from './types'; -import { isAlertType } from './types'; +import type { RulesClient } from '@kbn/alerting-plugin/server'; + +import type { + RuleObjectId, + RuleSignatureId, +} from '../../../../../../common/detection_engine/rule_schema'; +import { withSecuritySpan } from '../../../../../utils/with_security_span'; +import type { RuleParams } from '../../../rule_schema'; +import { isAlertType } from '../../../rule_schema'; +import { findRules } from '../search/find_rules'; + +export interface ReadRuleOptions { + rulesClient: RulesClient; + id: RuleObjectId | undefined; + ruleId: RuleSignatureId | undefined; +} /** * This reads the rules through a cascade try of what is fastest to what is slowest. diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.mock.ts similarity index 78% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.mock.ts index bdaf757cb086e..3e3da2e6f5530 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.mock.ts @@ -9,9 +9,9 @@ import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; import { getUpdateMachineLearningSchemaMock, getUpdateRulesSchemaMock, -} from '../../../../common/detection_engine/schemas/request/rule_schemas.mock'; -import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; +} from '../../../../../../common/detection_engine/rule_schema/mocks'; +import { getRuleMock } from '../../../routes/__mocks__/request_responses'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; export const getUpdateRulesOptionsMock = () => ({ rulesClient: rulesClientMock.create(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.test.ts similarity index 93% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.test.ts index 453a305d2e1ec..6abba4dba0ab9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.test.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { getRuleMock, resolveRuleMock } from '../routes/__mocks__/request_responses'; +import type { RulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock'; +import { getRuleMock, resolveRuleMock } from '../../../routes/__mocks__/request_responses'; +import { getMlRuleParams, getQueryRuleParams } from '../../../rule_schema/mocks'; import { updateRules } from './update_rules'; import { getUpdateRulesOptionsMock, getUpdateMlRulesOptionsMock } from './update_rules.mock'; -import type { RulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock'; -import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock'; // Failing with rule registry enabled describe('updateRules', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.ts similarity index 80% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.ts index c75e7534417e9..625204efbe4f6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.ts @@ -6,14 +6,21 @@ */ /* eslint-disable complexity */ -import type { PartialRule } from '@kbn/alerting-plugin/server'; -import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; -import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; +import type { PartialRule, RulesClient } from '@kbn/alerting-plugin/server'; +import { DEFAULT_MAX_SIGNALS } from '../../../../../../common/constants'; +import type { RuleUpdateProps } from '../../../../../../common/detection_engine/rule_schema'; +import { transformRuleToAlertAction } from '../../../../../../common/detection_engine/transform_actions'; -import type { UpdateRulesOptions } from './types'; -import { typeSpecificSnakeToCamel } from '../schemas/rule_converters'; -import type { InternalRuleUpdate, RuleParams } from '../schemas/rule_schemas'; -import { maybeMute, transformToAlertThrottle, transformToNotifyWhen } from './utils'; +import type { InternalRuleUpdate, RuleParams, RuleAlertType } from '../../../rule_schema'; +import { transformToAlertThrottle, transformToNotifyWhen } from '../../normalization/rule_actions'; +import { typeSpecificSnakeToCamel } from '../../normalization/rule_converters'; +import { maybeMute } from '../rule_actions/muting'; + +export interface UpdateRulesOptions { + rulesClient: RulesClient; + existingRule: RuleAlertType | null | undefined; + ruleUpdate: RuleUpdateProps; +} export const updateRules = async ({ rulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_for_default_rule_exception_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/exceptions/check_for_default_rule_exception_list.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_for_default_rule_exception_list.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/exceptions/check_for_default_rule_exception_list.test.ts index b95007d834b09..222badf14dfa6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_for_default_rule_exception_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/exceptions/check_for_default_rule_exception_list.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { checkDefaultRuleExceptionListReferences } from './check_for_default_rule_exception_list'; import type { ListArray } from '@kbn/securitysolution-io-ts-list-types'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import { checkDefaultRuleExceptionListReferences } from './check_for_default_rule_exception_list'; describe('checkDefaultRuleExceptionListReferences', () => { it('returns undefined if "exceptionLists" is undefined', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_for_default_rule_exception_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/exceptions/check_for_default_rule_exception_list.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_for_default_rule_exception_list.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/exceptions/check_for_default_rule_exception_list.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.test.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.test.ts index 25818c30f387b..b7395236a2152 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.test.ts @@ -5,25 +5,25 @@ * 2.0. */ -import type { FindHit } from '../routes/__mocks__/request_responses'; +import type { FindHit } from '../../../routes/__mocks__/request_responses'; import { getRuleMock, getFindResultWithSingleHit, getEmptySavedObjectsResponse, -} from '../routes/__mocks__/request_responses'; +} from '../../../routes/__mocks__/request_responses'; import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; import { getExportAll } from './get_export_all'; -import { getListArrayMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; -import { getThreatMock } from '../../../../common/detection_engine/schemas/types/threat.mock'; +import { getListArrayMock } from '../../../../../../common/detection_engine/schemas/types/lists.mock'; +import { getThreatMock } from '../../../../../../common/detection_engine/schemas/types/threat.mock'; import { getOutputDetailsSampleWithExceptions, getSampleDetailsAsNdjson, -} from '../../../../common/detection_engine/schemas/response/export_rules_details_schema.mock'; +} from '../../../../../../common/detection_engine/rule_management/mocks'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; import { getExceptionListClientMock } from '@kbn/lists-plugin/server/services/exception_lists/exception_list_client.mock'; import type { loggingSystemMock } from '@kbn/core/server/mocks'; -import { requestContextMock } from '../routes/__mocks__/request_context'; +import { requestContextMock } from '../../../routes/__mocks__/request_context'; const exceptionsClient = getExceptionListClientMock(); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts similarity index 87% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts index 60d75fdd64709..058559de7db59 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.ts @@ -10,13 +10,13 @@ import { transformDataToNdjson } from '@kbn/securitysolution-utils'; import type { Logger } from '@kbn/core/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { RulesClient, RuleExecutorServices } from '@kbn/alerting-plugin/server'; -import { getNonPackagedRules } from './get_existing_prepackaged_rules'; +import { getNonPackagedRules } from '../search/get_existing_prepackaged_rules'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; -import { transformAlertsToRules } from '../routes/rules/utils'; +import { transformAlertsToRules } from '../../utils/utils'; import { getRuleExceptionsForExport } from './get_export_rule_exceptions'; // eslint-disable-next-line no-restricted-imports -import { legacyGetBulkRuleActionsSavedObject } from '../rule_actions/legacy_get_bulk_rule_actions_saved_object'; +import { legacyGetBulkRuleActionsSavedObject } from '../../../rule_actions_legacy'; export const getExportAll = async ( rulesClient: RulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.test.ts similarity index 94% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.test.ts index b890315bf1977..0242f17509a99 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.test.ts @@ -7,25 +7,25 @@ import type { RulesErrors } from './get_export_by_object_ids'; import { getExportByObjectIds, getRulesFromObjects } from './get_export_by_object_ids'; -import type { FindHit } from '../routes/__mocks__/request_responses'; +import type { FindHit } from '../../../routes/__mocks__/request_responses'; import { getRuleMock, getFindResultWithSingleHit, getEmptySavedObjectsResponse, -} from '../routes/__mocks__/request_responses'; +} from '../../../routes/__mocks__/request_responses'; import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; -import { getListArrayMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; -import { getThreatMock } from '../../../../common/detection_engine/schemas/types/threat.mock'; +import { getListArrayMock } from '../../../../../../common/detection_engine/schemas/types/lists.mock'; +import { getThreatMock } from '../../../../../../common/detection_engine/schemas/types/threat.mock'; import { getSampleDetailsAsNdjson, getOutputDetailsSampleWithExceptions, -} from '../../../../common/detection_engine/schemas/response/export_rules_details_schema.mock'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; +} from '../../../../../../common/detection_engine/rule_management/mocks'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; import { getExceptionListClientMock } from '@kbn/lists-plugin/server/services/exception_lists/exception_list_client.mock'; const exceptionsClient = getExceptionListClientMock(); import type { loggingSystemMock } from '@kbn/core/server/mocks'; -import { requestContextMock } from '../routes/__mocks__/request_context'; +import { requestContextMock } from '../../../routes/__mocks__/request_context'; describe('get_export_by_object_ids', () => { let logger: ReturnType<typeof loggingSystemMock.createLogger>; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/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 similarity index 91% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_by_object_ids.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.ts index e044c8fdfd1ca..67da643d503e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/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 @@ -14,18 +14,18 @@ import type { RulesClient, RuleExecutorServices } from '@kbn/alerting-plugin/ser import { getExportDetailsNdjson } from './get_export_details_ndjson'; -import { isAlertType } from './types'; -import { findRules } from './find_rules'; +import { isAlertType } from '../../../rule_schema'; +import { findRules } from '../search/find_rules'; import { getRuleExceptionsForExport } from './get_export_rule_exceptions'; // eslint-disable-next-line no-restricted-imports -import { legacyGetBulkRuleActionsSavedObject } from '../rule_actions/legacy_get_bulk_rule_actions_saved_object'; -import { internalRuleToAPIResponse } from '../schemas/rule_converters'; -import type { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; +import { legacyGetBulkRuleActionsSavedObject } from '../../../rule_actions_legacy'; +import { internalRuleToAPIResponse } from '../../normalization/rule_converters'; +import type { RuleResponse } from '../../../../../../common/detection_engine/rule_schema'; interface ExportSuccessRule { statusCode: 200; - rule: FullResponseSchema; + rule: RuleResponse; } interface ExportFailedRule { @@ -36,7 +36,7 @@ interface ExportFailedRule { export interface RulesErrors { exportedCount: number; missingRules: Array<{ rule_id: string }>; - rules: FullResponseSchema[]; + rules: RuleResponse[]; } export const getExportByObjectIds = async ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_details_ndjson.test.ts similarity index 94% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_details_ndjson.test.ts index e58d1b5088fce..b380fc804233b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_details_ndjson.test.ts @@ -5,8 +5,8 @@ * 2.0. */ +import { getRulesSchemaMock } from '../../../../../../common/detection_engine/rule_schema/mocks'; import { getExportDetailsNdjson } from './get_export_details_ndjson'; -import { getRulesSchemaMock } from '../../../../common/detection_engine/schemas/response/rules_schema.mocks'; describe('getExportDetailsNdjson', () => { test('it ends with a new line character', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_details_ndjson.ts similarity index 79% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_details_ndjson.ts index 204d78f5fe7d2..465c4b53b1e51 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_details_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_details_ndjson.ts @@ -6,12 +6,11 @@ */ import type { ExportExceptionDetails } from '@kbn/securitysolution-io-ts-list-types'; -import type { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; - -import type { ExportRulesDetails } from '../../../../common/detection_engine/schemas/response/export_rules_details_schema'; +import type { ExportRulesDetails } from '../../../../../../common/detection_engine/rule_management'; +import type { RuleResponse } from '../../../../../../common/detection_engine/rule_schema'; export const getExportDetailsNdjson = ( - rules: FullResponseSchema[], + rules: RuleResponse[], missingRules: Array<{ rule_id: string }> = [], exceptionDetails?: ExportExceptionDetails ): string => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_rule_exceptions.test.ts similarity index 97% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_rule_exceptions.test.ts index 79507a73147a9..07360aeda2986 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_rule_exceptions.test.ts @@ -11,12 +11,12 @@ import { getExceptionListClientMock } from '@kbn/lists-plugin/server/services/ex import { getDetectionsExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; +import { getListMock } from '../../../../../../common/detection_engine/schemas/types/lists.mock'; import { getRuleExceptionsForExport, getExportableExceptions, getDefaultExportDetails, } from './get_export_rule_exceptions'; -import { getListMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; describe('get_export_rule_exceptions', () => { describe('getRuleExceptionsForExport', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_rule_exceptions.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_rule_exceptions.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.test.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.test.ts index d1fb4881ae9a8..fdee52eac1a24 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; -import { checkRuleExceptionReferences } from './check_rule_exception_references'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/mocks'; +import { checkRuleExceptionReferences } from './check_rule_exception_references'; describe('checkRuleExceptionReferences', () => { it('returns empty array if rule has no exception list references', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.ts similarity index 89% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.ts index a92d7e4ec1723..23494c3fc23dd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/check_rule_exception_references.ts @@ -6,9 +6,9 @@ */ import type { ListArray, ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; -import type { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; -import type { BulkError } from '../../utils'; -import { createBulkErrorObject } from '../../utils'; +import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; +import type { BulkError } from '../../../routes/utils'; +import { createBulkErrorObject } from '../../../routes/utils'; /** * Helper to check if all the exception lists referenced on a @@ -25,7 +25,7 @@ export const checkRuleExceptionReferences = ({ rule, existingLists, }: { - rule: ImportRulesSchema; + rule: RuleToImport; existingLists: Record<string, ExceptionListSchema>; }): [BulkError[], ListArray] => { let ruleExceptions: ListArray = []; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts similarity index 95% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts index 4b3483229af89..47a4a4832b034 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.test.ts @@ -9,14 +9,15 @@ import { Readable } from 'stream'; import { createPromiseFromStreams } from '@kbn/utils'; import { createRulesAndExceptionsStreamFromNdJson } from './create_rules_stream_from_ndjson'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; -import type { ImportRulesSchema } from '../../../../common/detection_engine/schemas/request/import_rules_schema'; + +import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; import { getOutputDetailsSample, getSampleDetailsAsNdjson, -} from '../../../../common/detection_engine/schemas/response/export_rules_details_schema.mock'; -import type { RuleExceptionsPromiseFromStreams } from '../routes/rules/utils/import_rules_utils'; +} from '../../../../../../common/detection_engine/rule_management/mocks'; +import type { RuleExceptionsPromiseFromStreams } from './import_rules_utils'; -export const getOutputSample = (): Partial<ImportRulesSchema> => ({ +export const getOutputSample = (): Partial<RuleToImport> => ({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -30,7 +31,7 @@ export const getOutputSample = (): Partial<ImportRulesSchema> => ({ type: 'query', }); -export const getSampleAsNdjson = (sample: Partial<ImportRulesSchema>): string => { +export const getSampleAsNdjson = (sample: Partial<RuleToImport>): string => { return `${JSON.stringify(sample)}\n`; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.ts similarity index 74% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.ts index 486b8b531a3fd..e325496225d9f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/create_rules_stream_from_ndjson.ts @@ -5,10 +5,11 @@ * 2.0. */ -import type { Transform } from 'stream'; -import type * as t from 'io-ts'; +import { has } from 'lodash/fp'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; +import type * as t from 'io-ts'; +import type { Transform } from 'stream'; import { createSplitStream, createMapStream, @@ -16,21 +17,22 @@ import { createReduceStream, } from '@kbn/utils'; -import { exactCheck, formatErrors } from '@kbn/securitysolution-io-ts-utils'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; +import { exactCheck, formatErrors } from '@kbn/securitysolution-io-ts-utils'; import type { ImportExceptionListItemSchema, ImportExceptionsListSchema, } from '@kbn/securitysolution-io-ts-list-types'; -import { has } from 'lodash/fp'; -import { importRuleValidateTypeDependents } from '../../../../common/detection_engine/schemas/request/import_rules_type_dependents'; -import type { ImportRulesSchema } from '../../../../common/detection_engine/schemas/request/import_rules_schema'; -import { importRulesSchema } from '../../../../common/detection_engine/schemas/request/import_rules_schema'; + +import { + RuleToImport, + validateRuleToImport, +} from '../../../../../../common/detection_engine/rule_management'; import { parseNdjsonStrings, createRulesLimitStream, filterExportedCounts, -} from '../../../utils/read_stream/create_stream_from_ndjson'; +} from '../../../../../utils/read_stream/create_stream_from_ndjson'; /** * Validates exception lists and items schemas @@ -38,25 +40,23 @@ import { export const validateRulesStream = (): Transform => { return createMapStream<{ exceptions: Array<ImportExceptionsListSchema | ImportExceptionListItemSchema | Error>; - rules: Array<ImportRulesSchema | Error>; + rules: Array<RuleToImport | Error>; }>((items) => ({ exceptions: items.exceptions, rules: validateRules(items.rules), })); }; -export const validateRules = ( - rules: Array<ImportRulesSchema | Error> -): Array<ImportRulesSchema | Error> => { - return rules.map((obj: ImportRulesSchema | Error) => { +export const validateRules = (rules: Array<RuleToImport | Error>): Array<RuleToImport | Error> => { + return rules.map((obj: RuleToImport | Error) => { if (!(obj instanceof Error)) { - const decoded = importRulesSchema.decode(obj); + const decoded = RuleToImport.decode(obj); const checked = exactCheck(obj, decoded); - const onLeft = (errors: t.Errors): BadRequestError | ImportRulesSchema => { + const onLeft = (errors: t.Errors): BadRequestError | RuleToImport => { return new BadRequestError(formatErrors(errors).join()); }; - const onRight = (schema: ImportRulesSchema): BadRequestError | ImportRulesSchema => { - const validationErrors = importRuleValidateTypeDependents(schema); + const onRight = (schema: RuleToImport): BadRequestError | RuleToImport => { + const validationErrors = validateRuleToImport(schema); if (validationErrors.length) { return new BadRequestError(validationErrors.join()); } else { @@ -79,7 +79,7 @@ export const validateRules = ( export const sortImports = (): Transform => { return createReduceStream<{ exceptions: Array<ImportExceptionsListSchema | ImportExceptionListItemSchema | Error>; - rules: Array<ImportRulesSchema | Error>; + rules: Array<RuleToImport | Error>; }>( (acc, importItem) => { if (has('list_id', importItem) || has('item_id', importItem) || has('entries', importItem)) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts new file mode 100644 index 0000000000000..5f903386ab012 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.test.ts @@ -0,0 +1,283 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { SavedObjectsClientContract } from '@kbn/core/server'; +import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { findExceptionList } from '@kbn/lists-plugin/server/services/exception_lists/find_exception_list'; +import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/mocks'; +import { + getReferencedExceptionLists, + parseReferencedExceptionsLists, +} from './gather_referenced_exceptions'; + +jest.mock('@kbn/lists-plugin/server/services/exception_lists/find_exception_list'); + +describe('get referenced exceptions', () => { + describe('getReferencedExceptions', () => { + let savedObjectsClient: jest.Mocked<SavedObjectsClientContract>; + + beforeEach(() => { + savedObjectsClient = savedObjectsClientMock.create(); + + (findExceptionList as jest.Mock).mockResolvedValue({ + data: [ + { + ...getExceptionListSchemaMock(), + id: '123', + list_id: 'my-list', + namespace_type: 'single', + type: 'detection', + }, + ], + page: 1, + per_page: 20, + total: 1, + }); + jest.clearAllMocks(); + }); + + it('returns empty object if no rules to search', async () => { + const result = await getReferencedExceptionLists({ + rules: [], + savedObjectsClient, + }); + + expect(result).toEqual({}); + }); + + it('returns found referenced exception lists', async () => { + const result = await getReferencedExceptionLists({ + rules: [ + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ], + savedObjectsClient, + }); + + expect(result).toEqual({ + 'my-list': { + ...getExceptionListSchemaMock(), + id: '123', + list_id: 'my-list', + namespace_type: 'single', + type: 'detection', + }, + }); + }); + + it('returns found referenced exception lists when first exceptions list is empty array and second list has a value', async () => { + const result = await getReferencedExceptionLists({ + rules: [ + { + ...getImportRulesSchemaMock(), + exceptions_list: [], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ], + savedObjectsClient, + }); + + expect(result).toEqual({ + 'my-list': { + ...getExceptionListSchemaMock(), + id: '123', + list_id: 'my-list', + namespace_type: 'single', + type: 'detection', + }, + }); + }); + + it('returns found referenced exception lists when two rules reference same list', async () => { + const result = await getReferencedExceptionLists({ + rules: [ + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ], + savedObjectsClient, + }); + + expect(result).toEqual({ + 'my-list': { + ...getExceptionListSchemaMock(), + id: '123', + list_id: 'my-list', + namespace_type: 'single', + type: 'detection', + }, + }); + }); + + it('returns two found referenced exception lists when two rules reference different lists', async () => { + (findExceptionList as jest.Mock).mockResolvedValue({ + data: [ + { + ...getExceptionListSchemaMock(), + id: '123', + list_id: 'my-list', + namespace_type: 'single', + type: 'detection', + }, + { + ...getExceptionListSchemaMock(), + id: '456', + list_id: 'other-list', + namespace_type: 'single', + type: 'detection', + }, + ], + page: 1, + per_page: 20, + total: 2, + }); + + const result = await getReferencedExceptionLists({ + rules: [ + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '456', list_id: 'other-list', namespace_type: 'single', type: 'detection' }, + ], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ], + savedObjectsClient, + }); + + // the problem with these tests is that they are entirely dependent on + // the result from the saved objects client matching what we put here + // so it essentially just bypasses the code that is not interacting with + // the saved objects client. + expect(result).toEqual({ + 'my-list': { + ...getExceptionListSchemaMock(), + id: '123', + list_id: 'my-list', + namespace_type: 'single', + type: 'detection', + }, + 'other-list': { + ...getExceptionListSchemaMock(), + id: '456', + list_id: 'other-list', + namespace_type: 'single', + type: 'detection', + }, + }); + }); + + it('returns empty object if no referenced exception lists found', async () => { + const result = await getReferencedExceptionLists({ + rules: [], + savedObjectsClient, + }); + + expect(result).toEqual({}); + }); + }); + describe('parseReferencdedExceptionsLists', () => { + it('should return parsed lists when exception lists are not empty', () => { + const res = parseReferencedExceptionsLists([ + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ]); + expect(res).toEqual([[], [{ listId: 'my-list', namespaceType: 'single' }]]); + }); + it('should return parsed lists when one empty exception list and one non-empty list', () => { + const res = parseReferencedExceptionsLists([ + { + ...getImportRulesSchemaMock(), + exceptions_list: [], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ]); + expect(res).toEqual([[], [{ listId: 'my-list', namespaceType: 'single' }]]); + }); + + it('should return parsed lists when two non-empty exception lists reference same list', () => { + const res = parseReferencedExceptionsLists([ + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ]); + expect(res).toEqual([ + [], + [ + { listId: 'my-list', namespaceType: 'single' }, + { listId: 'my-list', namespaceType: 'single' }, + ], + ]); + }); + + it('should return parsed lists when two non-empty exception lists reference differet lists', () => { + const res = parseReferencedExceptionsLists([ + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, + ], + }, + { + ...getImportRulesSchemaMock(), + exceptions_list: [ + { id: '456', list_id: 'other-list', namespace_type: 'single', type: 'detection' }, + ], + }, + ]); + expect(res).toEqual([ + [], + [ + { listId: 'my-list', namespaceType: 'single' }, + { listId: 'other-list', namespaceType: 'single' }, + ], + ]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts similarity index 65% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts index ce6ab5c84678f..6653e8ad54a6e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/gather_referenced_exceptions.ts @@ -8,32 +8,31 @@ import type { ExceptionListSchema, ListArray } from '@kbn/securitysolution-io-ts import type { SavedObjectsClientContract } from '@kbn/core/server'; import type { ExceptionListQueryInfo } from '@kbn/lists-plugin/server/services/exception_lists/utils/import/find_all_exception_list_types'; import { getAllListTypes } from '@kbn/lists-plugin/server/services/exception_lists/utils/import/find_all_exception_list_types'; -import type { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; +import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; /** - * Helper that takes rules, goes through their referenced exception lists and - * searches for them, returning an object with all those found, using list_id as keys - * @param rules {array} - * @param savedObjectsClient {object} - * @returns {Promise} an object with all referenced lists found, using list_id as keys + * splitting out the parsing of the lists from the fetching + * for easier and more compartmentalized testing + * @param rules Array<RuleToImport | Error> + * @returns [ExceptionListQueryInfo[], ExceptionListQueryInfo[]] */ -export const getReferencedExceptionLists = async ({ - rules, - savedObjectsClient, -}: { - rules: Array<ImportRulesSchema | Error>; - savedObjectsClient: SavedObjectsClientContract; -}): Promise<Record<string, ExceptionListSchema>> => { - const [lists] = rules.reduce<ListArray[]>((acc, rule) => { - if (!(rule instanceof Error) && rule.exceptions_list != null) { - return [...acc, rule.exceptions_list]; +export const parseReferencedExceptionsLists = ( + rules: Array<RuleToImport | Error> +): [ExceptionListQueryInfo[], ExceptionListQueryInfo[]] => { + const lists = rules.reduce<ListArray>((acc, rule) => { + if ( + !(rule instanceof Error) && + rule.exceptions_list != null && + rule.exceptions_list.length > 0 + ) { + return [...acc, ...rule.exceptions_list]; } else { return acc; } }, []); - if (lists == null) { - return {}; + if (lists == null || lists.length === 0) { + return [[], []]; } const [agnosticLists, nonAgnosticLists] = lists.reduce< @@ -49,6 +48,23 @@ export const getReferencedExceptionLists = async ({ }, [[], []] ); + return [agnosticLists, nonAgnosticLists]; +}; +/** + * Helper that takes rules, goes through their referenced exception lists and + * searches for them, returning an object with all those found, using list_id as keys + * @param rules {array} + * @param savedObjectsClient {object} + * @returns {Promise} an object with all referenced lists found, using list_id as keys + */ +export const getReferencedExceptionLists = async ({ + rules, + savedObjectsClient, +}: { + rules: Array<RuleToImport | Error>; + savedObjectsClient: SavedObjectsClientContract; +}): Promise<Record<string, ExceptionListSchema>> => { + const [agnosticLists, nonAgnosticLists] = parseReferencedExceptionsLists(rules); return getAllListTypes(agnosticLists, nonAgnosticLists, savedObjectsClient); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/hapi_readable_stream.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/hapi_readable_stream.ts new file mode 100644 index 0000000000000..420d264a617ac --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/hapi_readable_stream.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 { Readable } from 'stream'; + +export interface HapiReadableStream extends Readable { + hapi: { + filename: string; + }; +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rule_exceptions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rule_exceptions.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rule_exceptions.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rule_exceptions.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rule_exceptions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rule_exceptions.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rule_exceptions.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rule_exceptions.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts similarity index 94% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts index c98bac15d457c..2de1816e4541f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.test.ts @@ -5,20 +5,22 @@ * 2.0. */ -import { requestContextMock } from '../../__mocks__'; -import { importRules } from './import_rules_utils'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/rule_management/mocks'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; + +import { requestContextMock } from '../../../routes/__mocks__'; import { getRuleMock, getEmptyFindResult, getFindResultWithSingleHit, -} from '../../__mocks__/request_responses'; -import { getQueryRuleParams } from '../../../schemas/rule_schemas.mock'; -import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; -import { createRules } from '../../../rules/create_rules'; -import { patchRules } from '../../../rules/patch_rules'; +} from '../../../routes/__mocks__/request_responses'; + +import { createRules } from '../crud/create_rules'; +import { patchRules } from '../crud/patch_rules'; +import { importRules } from './import_rules_utils'; -jest.mock('../../../rules/create_rules'); -jest.mock('../../../rules/patch_rules'); +jest.mock('../crud/create_rules'); +jest.mock('../crud/patch_rules'); describe('importRules', () => { const mlAuthz = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.ts similarity index 91% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.ts index a623f3887276a..694bc916fefe3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/import/import_rules_utils.ts @@ -14,18 +14,20 @@ import type { import type { RulesClient } from '@kbn/alerting-plugin/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; -import { legacyMigrate } from '../../../rules/utils'; -import type { ImportRuleResponse } from '../../utils'; -import { createBulkErrorObject } from '../../utils'; -import { createRules } from '../../../rules/create_rules'; -import { readRules } from '../../../rules/read_rules'; -import { patchRules } from '../../../rules/patch_rules'; -import type { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; + +import type { RuleToImport } from '../../../../../../common/detection_engine/rule_management'; +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate } from '../rule_actions/legacy_action_migration'; +import type { ImportRuleResponse } from '../../../routes/utils'; +import { createBulkErrorObject } from '../../../routes/utils'; +import { createRules } from '../crud/create_rules'; +import { readRules } from '../crud/read_rules'; +import { patchRules } from '../crud/patch_rules'; import type { MlAuthz } from '../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../machine_learning/validation'; import { checkRuleExceptionReferences } from './check_rule_exception_references'; -export type PromiseFromStreams = ImportRulesSchema | Error; +export type PromiseFromStreams = RuleToImport | Error; export interface RuleExceptionsPromiseFromStreams { rules: PromiseFromStreams[]; exceptions: Array<ImportExceptionsListSchema | ImportExceptionListItemSchema>; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.test.ts similarity index 56% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.test.ts index eb5d877d01942..f8332d5c3f3b1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.test.ts @@ -5,33 +5,21 @@ * 2.0. */ -import { - transformToNotifyWhen, - transformToAlertThrottle, - transformFromAlertThrottle, - transformActions, - legacyMigrate, - getUpdatedActionsParams, -} from './utils'; -import type { RuleAction, SanitizedRule } from '@kbn/alerting-plugin/common'; -import type { RuleParams } from '../schemas/rule_schemas'; -import { - NOTIFICATION_THROTTLE_NO_ACTIONS, - NOTIFICATION_THROTTLE_RULE, -} from '../../../../common/constants'; -import type { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyRuleActions } from '../rule_actions/legacy_types'; +import { requestContextMock } from '../../../routes/__mocks__'; import { getEmptyFindResult, - legacyGetSiemNotificationRuleActionsSOResultWithSingleHit, legacyGetDailyNotificationResult, legacyGetHourlyNotificationResult, + legacyGetSiemNotificationRuleActionsSOResultWithSingleHit, legacyGetWeeklyNotificationResult, -} from '../routes/__mocks__/request_responses'; -import { requestContextMock } from '../routes/__mocks__'; +} from '../../../routes/__mocks__/request_responses'; -const getRuleLegacyActions = (): SanitizedRule<RuleParams> => +import type { RuleAlertType } from '../../../rule_schema'; + +// eslint-disable-next-line no-restricted-imports +import { legacyMigrate, getUpdatedActionsParams } from './legacy_action_migration'; + +const getRuleLegacyActions = (): RuleAlertType => ({ id: '123', notifyWhen: 'onThrottleInterval', @@ -82,377 +70,10 @@ const getRuleLegacyActions = (): SanitizedRule<RuleParams> => lastExecutionDate: '2022-03-31T21:47:25.695Z', lastDuration: 0, }, - } as unknown as SanitizedRule<RuleParams>); - -describe('utils', () => { - describe('#transformToNotifyWhen', () => { - test('"null" throttle returns "null" notify', () => { - expect(transformToNotifyWhen(null)).toEqual(null); - }); - - test('"undefined" throttle returns "null" notify', () => { - expect(transformToNotifyWhen(undefined)).toEqual(null); - }); - - test('"NOTIFICATION_THROTTLE_NO_ACTIONS" throttle returns "null" notify', () => { - expect(transformToNotifyWhen(NOTIFICATION_THROTTLE_NO_ACTIONS)).toEqual(null); - }); - - test('"NOTIFICATION_THROTTLE_RULE" throttle returns "onActiveAlert" notify', () => { - expect(transformToNotifyWhen(NOTIFICATION_THROTTLE_RULE)).toEqual('onActiveAlert'); - }); - - test('"1h" throttle returns "onThrottleInterval" notify', () => { - expect(transformToNotifyWhen('1d')).toEqual('onThrottleInterval'); - }); - - test('"1d" throttle returns "onThrottleInterval" notify', () => { - expect(transformToNotifyWhen('1d')).toEqual('onThrottleInterval'); - }); - - test('"7d" throttle returns "onThrottleInterval" notify', () => { - expect(transformToNotifyWhen('7d')).toEqual('onThrottleInterval'); - }); - }); - - describe('#transformToAlertThrottle', () => { - test('"null" throttle returns "null" alert throttle', () => { - expect(transformToAlertThrottle(null)).toEqual(null); - }); - - test('"undefined" throttle returns "null" alert throttle', () => { - expect(transformToAlertThrottle(undefined)).toEqual(null); - }); - - test('"NOTIFICATION_THROTTLE_NO_ACTIONS" throttle returns "null" alert throttle', () => { - expect(transformToAlertThrottle(NOTIFICATION_THROTTLE_NO_ACTIONS)).toEqual(null); - }); - - test('"NOTIFICATION_THROTTLE_RULE" throttle returns "null" alert throttle', () => { - expect(transformToAlertThrottle(NOTIFICATION_THROTTLE_RULE)).toEqual(null); - }); - - test('"1h" throttle returns "1h" alert throttle', () => { - expect(transformToAlertThrottle('1h')).toEqual('1h'); - }); - - test('"1d" throttle returns "1d" alert throttle', () => { - expect(transformToAlertThrottle('1d')).toEqual('1d'); - }); - - test('"7d" throttle returns "7d" alert throttle', () => { - expect(transformToAlertThrottle('7d')).toEqual('7d'); - }); - }); - - describe('#transformFromAlertThrottle', () => { - test('muteAll returns "NOTIFICATION_THROTTLE_NO_ACTIONS" even with notifyWhen set and actions has an array element', () => { - expect( - transformFromAlertThrottle( - { - muteAll: true, - notifyWhen: 'onActiveAlert', - actions: [ - { - group: 'group', - id: 'id-123', - actionTypeId: 'id-456', - params: {}, - }, - ], - } as SanitizedRule<RuleParams>, - undefined - ) - ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); - }); - - test('returns "NOTIFICATION_THROTTLE_NO_ACTIONS" if actions is an empty array and we do not have a throttle', () => { - expect( - transformFromAlertThrottle( - { - muteAll: false, - notifyWhen: 'onActiveAlert', - actions: [], - } as unknown as SanitizedRule<RuleParams>, - undefined - ) - ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); - }); - - test('returns "NOTIFICATION_THROTTLE_NO_ACTIONS" if actions is an empty array and we have a throttle', () => { - expect( - transformFromAlertThrottle( - { - muteAll: false, - notifyWhen: 'onThrottleInterval', - actions: [], - throttle: '1d', - } as unknown as SanitizedRule<RuleParams>, - undefined - ) - ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); - }); - - test('it returns "NOTIFICATION_THROTTLE_RULE" if "notifyWhen" is set, muteAll is false and we have an actions array', () => { - expect( - transformFromAlertThrottle( - { - muteAll: false, - notifyWhen: 'onActiveAlert', - actions: [ - { - group: 'group', - id: 'id-123', - actionTypeId: 'id-456', - params: {}, - }, - ], - } as SanitizedRule<RuleParams>, - undefined - ) - ).toEqual(NOTIFICATION_THROTTLE_RULE); - }); - - test('it returns "NOTIFICATION_THROTTLE_RULE" if "notifyWhen" and "throttle" are not set, but we have an actions array', () => { - expect( - transformFromAlertThrottle( - { - muteAll: false, - actions: [ - { - group: 'group', - id: 'id-123', - actionTypeId: 'id-456', - params: {}, - }, - ], - } as SanitizedRule<RuleParams>, - undefined - ) - ).toEqual(NOTIFICATION_THROTTLE_RULE); - }); - - test('it will use the "rule" and not the "legacyRuleActions" if the rule and actions is defined', () => { - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: '', - alertThrottle: '', - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - - expect( - transformFromAlertThrottle( - { - muteAll: true, - notifyWhen: 'onActiveAlert', - actions: [ - { - group: 'group', - id: 'id-123', - actionTypeId: 'id-456', - params: {}, - }, - ], - } as SanitizedRule<RuleParams>, - legacyRuleActions - ) - ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); - }); - - test('it will use the "legacyRuleActions" and not the "rule" if the rule actions is an empty array', () => { - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: NOTIFICATION_THROTTLE_RULE, - alertThrottle: null, - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - - expect( - transformFromAlertThrottle( - { - muteAll: true, - notifyWhen: 'onActiveAlert', - actions: [], - } as unknown as SanitizedRule<RuleParams>, - legacyRuleActions - ) - ).toEqual(NOTIFICATION_THROTTLE_RULE); - }); - - test('it will use the "legacyRuleActions" and not the "rule" if the rule actions is a null', () => { - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: NOTIFICATION_THROTTLE_RULE, - alertThrottle: null, - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - - expect( - transformFromAlertThrottle( - { - muteAll: true, - notifyWhen: 'onActiveAlert', - actions: null, - } as unknown as SanitizedRule<RuleParams>, - legacyRuleActions - ) - ).toEqual(NOTIFICATION_THROTTLE_RULE); - }); - }); - - describe('#transformActions', () => { - test('It transforms two alert actions', () => { - const alertAction: RuleAction[] = [ - { - id: 'id_1', - group: 'group', - actionTypeId: 'actionTypeId', - params: {}, - }, - { - id: 'id_2', - group: 'group', - actionTypeId: 'actionTypeId', - params: {}, - }, - ]; - - const transformed = transformActions(alertAction, null); - expect(transformed).toEqual<FullResponseSchema['actions']>([ - { - id: 'id_1', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ]); - }); - - test('It transforms two alert actions but not a legacyRuleActions if this is also passed in', () => { - const alertAction: RuleAction[] = [ - { - id: 'id_1', - group: 'group', - actionTypeId: 'actionTypeId', - params: {}, - }, - { - id: 'id_2', - group: 'group', - actionTypeId: 'actionTypeId', - params: {}, - }, - ]; - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: '', - alertThrottle: '', - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - const transformed = transformActions(alertAction, legacyRuleActions); - expect(transformed).toEqual<FullResponseSchema['actions']>([ - { - id: 'id_1', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ]); - }); - - test('It will transform the legacyRuleActions if the alertAction is an empty array', () => { - const alertAction: RuleAction[] = []; - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: '', - alertThrottle: '', - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - const transformed = transformActions(alertAction, legacyRuleActions); - expect(transformed).toEqual<FullResponseSchema['actions']>([ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ]); - }); - - test('It will transform the legacyRuleActions if the alertAction is undefined', () => { - const legacyRuleActions: LegacyRuleActions = { - id: 'id_1', - ruleThrottle: '', - alertThrottle: '', - actions: [ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ], - }; - const transformed = transformActions(undefined, legacyRuleActions); - expect(transformed).toEqual<FullResponseSchema['actions']>([ - { - id: 'id_2', - group: 'group', - action_type_id: 'actionTypeId', - params: {}, - }, - ]); - }); - }); + } as unknown as RuleAlertType); - describe('#legacyMigrate', () => { +describe('Legacy rule action migration logic', () => { + describe('legacyMigrate', () => { const ruleId = '123'; const connectorId = '456'; const { clients } = requestContextMock.createTools(); @@ -477,7 +98,7 @@ describe('utils', () => { throttle: null, notifyWhen: 'onActiveAlert', muteAll: true, - } as SanitizedRule<RuleParams>; + } as RuleAlertType; const migratedRule = await legacyMigrate({ rulesClient: clients.rulesClient, @@ -720,7 +341,7 @@ describe('utils', () => { }); }); - describe('#getUpdatedActionsParams', () => { + describe('getUpdatedActionsParams', () => { it('updates one action', () => { const { id, ...rule } = { ...getRuleLegacyActions(), @@ -728,7 +349,7 @@ describe('utils', () => { actions: [], throttle: null, notifyWhen: 'onActiveAlert', - } as SanitizedRule<RuleParams>; + } as RuleAlertType; expect( getUpdatedActionsParams({ @@ -788,7 +409,7 @@ describe('utils', () => { actions: [], throttle: null, notifyWhen: 'onActiveAlert', - } as SanitizedRule<RuleParams>; + } as RuleAlertType; expect( getUpdatedActionsParams({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.ts new file mode 100644 index 0000000000000..8c69edc33064b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/legacy_action_migration.ts @@ -0,0 +1,178 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 type { RuleAction } from '@kbn/alerting-plugin/common'; +import type { RulesClient } from '@kbn/alerting-plugin/server'; +import type { SavedObjectReference, SavedObjectsClientContract } from '@kbn/core/server'; + +import { withSecuritySpan } from '../../../../../utils/with_security_span'; +import type { RuleAlertType } from '../../../rule_schema'; + +// eslint-disable-next-line no-restricted-imports +import { legacyRuleActionsSavedObjectType } from '../../../rule_actions_legacy'; +// eslint-disable-next-line no-restricted-imports +import type { + LegacyIRuleActionsAttributes, + LegacyRuleAlertSavedObjectAction, +} from '../../../rule_actions_legacy'; + +import { transformToAlertThrottle, transformToNotifyWhen } from '../../normalization/rule_actions'; + +export interface LegacyMigrateParams { + rulesClient: RulesClient; + savedObjectsClient: SavedObjectsClientContract; + rule: RuleAlertType | null | undefined; +} + +/** + * Determines if rule needs to be migrated from legacy actions + * and returns necessary pieces for the updated rule + */ +export const legacyMigrate = async ({ + rulesClient, + savedObjectsClient, + rule, +}: LegacyMigrateParams): Promise<RuleAlertType | null | undefined> => + withSecuritySpan('legacyMigrate', async () => { + if (rule == null || rule.id == null) { + return rule; + } + /** + * On update / patch I'm going to take the actions as they are, better off taking rules client.find (siem.notification) result + * and putting that into the actions array of the rule, then set the rules onThrottle property, notifyWhen and throttle from null -> actual value (1hr etc..) + * Then use the rules client to delete the siem.notification + * Then with the legacy Rule Actions saved object type, just delete it. + */ + // find it using the references array, not params.ruleAlertId + const [siemNotification, legacyRuleActionsSO] = await Promise.all([ + rulesClient.find({ + options: { + filter: 'alert.attributes.alertTypeId:(siem.notifications)', + hasReference: { + type: 'alert', + id: rule.id, + }, + }, + }), + savedObjectsClient.find<LegacyIRuleActionsAttributes>({ + type: legacyRuleActionsSavedObjectType, + hasReference: { + type: 'alert', + id: rule.id, + }, + }), + ]); + + const siemNotificationsExist = siemNotification != null && siemNotification.data.length > 0; + const legacyRuleNotificationSOsExist = + legacyRuleActionsSO != null && legacyRuleActionsSO.saved_objects.length > 0; + + // Assumption: if no legacy sidecar SO or notification rule types exist + // that reference the rule in question, assume rule actions are not legacy + if (!siemNotificationsExist && !legacyRuleNotificationSOsExist) { + return rule; + } + // If the legacy notification rule type ("siem.notification") exist, + // migration and cleanup are needed + if (siemNotificationsExist) { + await rulesClient.delete({ id: siemNotification.data[0].id }); + } + // If legacy notification sidecar ("siem-detection-engine-rule-actions") + // exist, migration and cleanup are needed + if (legacyRuleNotificationSOsExist) { + // Delete the legacy sidecar SO + await savedObjectsClient.delete( + legacyRuleActionsSavedObjectType, + legacyRuleActionsSO.saved_objects[0].id + ); + + // If "siem-detection-engine-rule-actions" notes that `ruleThrottle` is + // "no_actions" or "rule", rule has no actions or rule is set to run + // action on every rule run. In these cases, sidecar deletion is the only + // cleanup needed and updates to the "throttle" and "notifyWhen". "siem.notification" are + // not created for these action types + if ( + legacyRuleActionsSO.saved_objects[0].attributes.ruleThrottle === 'no_actions' || + legacyRuleActionsSO.saved_objects[0].attributes.ruleThrottle === 'rule' + ) { + return rule; + } + + // Use "legacyRuleActionsSO" instead of "siemNotification" as "siemNotification" is not created + // until a rule is run and added to task manager. That means that if by chance a user has a rule + // with actions which they have yet to enable, the actions would be lost. Instead, + // "legacyRuleActionsSO" is created on rule creation (pre 7.15) and we can rely on it to be there + const migratedRule = getUpdatedActionsParams({ + rule, + ruleThrottle: legacyRuleActionsSO.saved_objects[0].attributes.ruleThrottle, + actions: legacyRuleActionsSO.saved_objects[0].attributes.actions, + references: legacyRuleActionsSO.saved_objects[0].references, + }); + + await rulesClient.update({ + id: rule.id, + data: migratedRule, + }); + + return { id: rule.id, ...migratedRule }; + } + }); + +/** + * Translate legacy action sidecar action to rule action + */ +export const getUpdatedActionsParams = ({ + rule, + ruleThrottle, + actions, + references, +}: { + rule: RuleAlertType; + ruleThrottle: string | null; + actions: LegacyRuleAlertSavedObjectAction[]; + references: SavedObjectReference[]; +}): Omit<RuleAlertType, 'id'> => { + const { id, ...restOfRule } = rule; + + const actionReference = references.reduce<Record<string, SavedObjectReference>>( + (acc, reference) => { + acc[reference.name] = reference; + return acc; + }, + {} + ); + + if (isEmpty(actionReference)) { + throw new Error( + `An error occurred migrating legacy action for rule with id:${id}. Connector reference id not found.` + ); + } + // If rule has an action on any other interval (other than on every + // rule run), need to move the action info from the sidecar/legacy action + // into the rule itself + return { + ...restOfRule, + actions: actions.reduce<RuleAction[]>((acc, action) => { + const { actionRef, action_type_id: actionTypeId, ...resOfAction } = action; + if (!actionReference[actionRef]) { + return acc; + } + return [ + ...acc, + { + ...resOfAction, + id: actionReference[actionRef].id, + actionTypeId, + }, + ]; + }, []), + throttle: transformToAlertThrottle(ruleThrottle), + notifyWhen: transformToNotifyWhen(ruleThrottle), + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/muting.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/muting.ts new file mode 100644 index 0000000000000..6c0c7d9686767 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/rule_actions/muting.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 type { RulesClient } from '@kbn/alerting-plugin/server'; +import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../../common/constants'; +import type { RuleAlertType } from '../../../rule_schema'; + +/** + * Mutes, unmutes, or does nothing to the alert if no changed is detected + * @param id The id of the alert to (un)mute + * @param rulesClient the rules client + * @param muteAll If the existing alert has all actions muted + * @param throttle If the existing alert has a throttle set + */ +export const maybeMute = async ({ + id, + rulesClient, + muteAll, + throttle, +}: { + id: RuleAlertType['id']; + rulesClient: RulesClient; + muteAll: RuleAlertType['muteAll']; + throttle: string | null | undefined; +}): Promise<void> => { + if (muteAll && throttle !== NOTIFICATION_THROTTLE_NO_ACTIONS) { + await rulesClient.unmuteAll({ id }); + } else if (!muteAll && throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { + await rulesClient.muteAll({ id }); + } else { + // Do nothing, no-operation + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/enrich_filter_with_rule_type_mappings.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/enrich_filter_with_rule_type_mappings.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/enrich_filter_with_rule_type_mappings.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/enrich_filter_with_rule_type_mappings.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/enrich_filter_with_rule_type_mappings.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/enrich_filter_with_rule_type_mappings.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/enrich_filter_with_rule_type_mappings.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/enrich_filter_with_rule_type_mappings.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts similarity index 53% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts index db45306420bb8..bb46831417d71 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/find_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/find_rules.ts @@ -5,11 +5,29 @@ * 2.0. */ -import type { FindResult } from '@kbn/alerting-plugin/server'; +import type { FindResult, RulesClient } from '@kbn/alerting-plugin/server'; + +import type { + FieldsOrUndefined, + PageOrUndefined, + PerPageOrUndefined, + QueryFilterOrUndefined, + SortFieldOrUndefined, + SortOrderOrUndefined, +} from '../../../../../../common/detection_engine/schemas/common'; + +import type { RuleParams } from '../../../rule_schema'; import { enrichFilterWithRuleTypeMapping } from './enrich_filter_with_rule_type_mappings'; -import type { RuleParams } from '../schemas/rule_schemas'; -import type { FindRuleOptions } from './types'; +export interface FindRuleOptions { + rulesClient: RulesClient; + filter: QueryFilterOrUndefined; + fields: FieldsOrUndefined; + sortField: SortFieldOrUndefined; + sortOrder: SortOrderOrUndefined; + page: PageOrUndefined; + perPage: PerPageOrUndefined; +} export const findRules = ({ rulesClient, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.test.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.test.ts index 279a211c9ea33..338a0239b6250 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.test.ts @@ -10,8 +10,8 @@ import { getRuleMock, getFindResultWithSingleHit, getFindResultWithMultiHits, -} from '../routes/__mocks__/request_responses'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; +} from '../../../routes/__mocks__/request_responses'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; import { getExistingPrepackagedRules, getNonPackagedRules, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.ts similarity index 93% rename from x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.ts index 0536a7e0a264d..469de8544a13a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.ts @@ -6,9 +6,9 @@ */ import type { RulesClient } from '@kbn/alerting-plugin/server'; -import { withSecuritySpan } from '../../../utils/with_security_span'; +import { withSecuritySpan } from '../../../../../utils/with_security_span'; import { findRules } from './find_rules'; -import type { RuleAlertType } from './types'; +import type { RuleAlertType } from '../../../rule_schema'; export const FILTER_NON_PREPACKED_RULES = 'alert.attributes.params.immutable: false'; export const FILTER_PREPACKED_RULES = 'alert.attributes.params.immutable: true'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.test.ts new file mode 100644 index 0000000000000..419e84d4d8373 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.test.ts @@ -0,0 +1,394 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { RuleAction } from '@kbn/alerting-plugin/common'; + +import { + NOTIFICATION_THROTTLE_NO_ACTIONS, + NOTIFICATION_THROTTLE_RULE, +} from '../../../../../common/constants'; + +import type { RuleResponse } from '../../../../../common/detection_engine/rule_schema'; +// eslint-disable-next-line no-restricted-imports +import type { LegacyRuleActions } from '../../rule_actions_legacy'; +import type { RuleAlertType } from '../../rule_schema'; + +import { + transformActions, + transformFromAlertThrottle, + transformToAlertThrottle, + transformToNotifyWhen, +} from './rule_actions'; + +describe('Rule actions normalization', () => { + describe('transformToNotifyWhen', () => { + test('"null" throttle returns "null" notify', () => { + expect(transformToNotifyWhen(null)).toEqual(null); + }); + + test('"undefined" throttle returns "null" notify', () => { + expect(transformToNotifyWhen(undefined)).toEqual(null); + }); + + test('"NOTIFICATION_THROTTLE_NO_ACTIONS" throttle returns "null" notify', () => { + expect(transformToNotifyWhen(NOTIFICATION_THROTTLE_NO_ACTIONS)).toEqual(null); + }); + + test('"NOTIFICATION_THROTTLE_RULE" throttle returns "onActiveAlert" notify', () => { + expect(transformToNotifyWhen(NOTIFICATION_THROTTLE_RULE)).toEqual('onActiveAlert'); + }); + + test('"1h" throttle returns "onThrottleInterval" notify', () => { + expect(transformToNotifyWhen('1d')).toEqual('onThrottleInterval'); + }); + + test('"1d" throttle returns "onThrottleInterval" notify', () => { + expect(transformToNotifyWhen('1d')).toEqual('onThrottleInterval'); + }); + + test('"7d" throttle returns "onThrottleInterval" notify', () => { + expect(transformToNotifyWhen('7d')).toEqual('onThrottleInterval'); + }); + }); + + describe('transformToAlertThrottle', () => { + test('"null" throttle returns "null" alert throttle', () => { + expect(transformToAlertThrottle(null)).toEqual(null); + }); + + test('"undefined" throttle returns "null" alert throttle', () => { + expect(transformToAlertThrottle(undefined)).toEqual(null); + }); + + test('"NOTIFICATION_THROTTLE_NO_ACTIONS" throttle returns "null" alert throttle', () => { + expect(transformToAlertThrottle(NOTIFICATION_THROTTLE_NO_ACTIONS)).toEqual(null); + }); + + test('"NOTIFICATION_THROTTLE_RULE" throttle returns "null" alert throttle', () => { + expect(transformToAlertThrottle(NOTIFICATION_THROTTLE_RULE)).toEqual(null); + }); + + test('"1h" throttle returns "1h" alert throttle', () => { + expect(transformToAlertThrottle('1h')).toEqual('1h'); + }); + + test('"1d" throttle returns "1d" alert throttle', () => { + expect(transformToAlertThrottle('1d')).toEqual('1d'); + }); + + test('"7d" throttle returns "7d" alert throttle', () => { + expect(transformToAlertThrottle('7d')).toEqual('7d'); + }); + }); + + describe('transformFromAlertThrottle', () => { + test('muteAll returns "NOTIFICATION_THROTTLE_NO_ACTIONS" even with notifyWhen set and actions has an array element', () => { + expect( + transformFromAlertThrottle( + { + muteAll: true, + notifyWhen: 'onActiveAlert', + actions: [ + { + group: 'group', + id: 'id-123', + actionTypeId: 'id-456', + params: {}, + }, + ], + } as RuleAlertType, + undefined + ) + ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); + }); + + test('returns "NOTIFICATION_THROTTLE_NO_ACTIONS" if actions is an empty array and we do not have a throttle', () => { + expect( + transformFromAlertThrottle( + { + muteAll: false, + notifyWhen: 'onActiveAlert', + actions: [], + } as unknown as RuleAlertType, + undefined + ) + ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); + }); + + test('returns "NOTIFICATION_THROTTLE_NO_ACTIONS" if actions is an empty array and we have a throttle', () => { + expect( + transformFromAlertThrottle( + { + muteAll: false, + notifyWhen: 'onThrottleInterval', + actions: [], + throttle: '1d', + } as unknown as RuleAlertType, + undefined + ) + ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); + }); + + test('it returns "NOTIFICATION_THROTTLE_RULE" if "notifyWhen" is set, muteAll is false and we have an actions array', () => { + expect( + transformFromAlertThrottle( + { + muteAll: false, + notifyWhen: 'onActiveAlert', + actions: [ + { + group: 'group', + id: 'id-123', + actionTypeId: 'id-456', + params: {}, + }, + ], + } as RuleAlertType, + undefined + ) + ).toEqual(NOTIFICATION_THROTTLE_RULE); + }); + + test('it returns "NOTIFICATION_THROTTLE_RULE" if "notifyWhen" and "throttle" are not set, but we have an actions array', () => { + expect( + transformFromAlertThrottle( + { + muteAll: false, + actions: [ + { + group: 'group', + id: 'id-123', + actionTypeId: 'id-456', + params: {}, + }, + ], + } as RuleAlertType, + undefined + ) + ).toEqual(NOTIFICATION_THROTTLE_RULE); + }); + + test('it will use the "rule" and not the "legacyRuleActions" if the rule and actions is defined', () => { + const legacyRuleActions: LegacyRuleActions = { + id: 'id_1', + ruleThrottle: '', + alertThrottle: '', + actions: [ + { + id: 'id_2', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + ], + }; + + expect( + transformFromAlertThrottle( + { + muteAll: true, + notifyWhen: 'onActiveAlert', + actions: [ + { + group: 'group', + id: 'id-123', + actionTypeId: 'id-456', + params: {}, + }, + ], + } as RuleAlertType, + legacyRuleActions + ) + ).toEqual(NOTIFICATION_THROTTLE_NO_ACTIONS); + }); + + test('it will use the "legacyRuleActions" and not the "rule" if the rule actions is an empty array', () => { + const legacyRuleActions: LegacyRuleActions = { + id: 'id_1', + ruleThrottle: NOTIFICATION_THROTTLE_RULE, + alertThrottle: null, + actions: [ + { + id: 'id_2', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + ], + }; + + expect( + transformFromAlertThrottle( + { + muteAll: true, + notifyWhen: 'onActiveAlert', + actions: [], + } as unknown as RuleAlertType, + legacyRuleActions + ) + ).toEqual(NOTIFICATION_THROTTLE_RULE); + }); + + test('it will use the "legacyRuleActions" and not the "rule" if the rule actions is a null', () => { + const legacyRuleActions: LegacyRuleActions = { + id: 'id_1', + ruleThrottle: NOTIFICATION_THROTTLE_RULE, + alertThrottle: null, + actions: [ + { + id: 'id_2', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + ], + }; + + expect( + transformFromAlertThrottle( + { + muteAll: true, + notifyWhen: 'onActiveAlert', + actions: null, + } as unknown as RuleAlertType, + legacyRuleActions + ) + ).toEqual(NOTIFICATION_THROTTLE_RULE); + }); + }); + + describe('transformActions', () => { + test('It transforms two alert actions', () => { + const alertAction: RuleAction[] = [ + { + id: 'id_1', + group: 'group', + actionTypeId: 'actionTypeId', + params: {}, + }, + { + id: 'id_2', + group: 'group', + actionTypeId: 'actionTypeId', + params: {}, + }, + ]; + + const transformed = transformActions(alertAction, null); + expect(transformed).toEqual<RuleResponse['actions']>([ + { + id: 'id_1', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + { + id: 'id_2', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + ]); + }); + + test('It transforms two alert actions but not a legacyRuleActions if this is also passed in', () => { + const alertAction: RuleAction[] = [ + { + id: 'id_1', + group: 'group', + actionTypeId: 'actionTypeId', + params: {}, + }, + { + id: 'id_2', + group: 'group', + actionTypeId: 'actionTypeId', + params: {}, + }, + ]; + const legacyRuleActions: LegacyRuleActions = { + id: 'id_1', + ruleThrottle: '', + alertThrottle: '', + actions: [ + { + id: 'id_2', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + ], + }; + const transformed = transformActions(alertAction, legacyRuleActions); + expect(transformed).toEqual<RuleResponse['actions']>([ + { + id: 'id_1', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + { + id: 'id_2', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + ]); + }); + + test('It will transform the legacyRuleActions if the alertAction is an empty array', () => { + const alertAction: RuleAction[] = []; + const legacyRuleActions: LegacyRuleActions = { + id: 'id_1', + ruleThrottle: '', + alertThrottle: '', + actions: [ + { + id: 'id_2', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + ], + }; + const transformed = transformActions(alertAction, legacyRuleActions); + expect(transformed).toEqual<RuleResponse['actions']>([ + { + id: 'id_2', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + ]); + }); + + test('It will transform the legacyRuleActions if the alertAction is undefined', () => { + const legacyRuleActions: LegacyRuleActions = { + id: 'id_1', + ruleThrottle: '', + alertThrottle: '', + actions: [ + { + id: 'id_2', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + ], + }; + const transformed = transformActions(undefined, legacyRuleActions); + expect(transformed).toEqual<RuleResponse['actions']>([ + { + id: 'id_2', + group: 'group', + action_type_id: 'actionTypeId', + params: {}, + }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.ts new file mode 100644 index 0000000000000..99b97ea6d89ec --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_actions.ts @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RuleAction, RuleNotifyWhenType } from '@kbn/alerting-plugin/common'; + +import { + NOTIFICATION_THROTTLE_NO_ACTIONS, + NOTIFICATION_THROTTLE_RULE, +} from '../../../../../common/constants'; + +import type { RuleResponse } from '../../../../../common/detection_engine/rule_schema'; +import { transformAlertToRuleAction } from '../../../../../common/detection_engine/transform_actions'; +// eslint-disable-next-line no-restricted-imports +import type { LegacyRuleActions } from '../../rule_actions_legacy'; +import type { RuleAlertType } from '../../rule_schema'; + +/** + * Given a throttle from a "security_solution" rule this will transform it into an "alerting" notifyWhen + * on their saved object. + * @params throttle The throttle from a "security_solution" rule + * @returns The correct "NotifyWhen" for a Kibana alerting. + */ +export const transformToNotifyWhen = ( + throttle: string | null | undefined +): RuleNotifyWhenType | null => { + if (throttle == null || throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { + return null; // Although I return null, this does not change the value of the "notifyWhen" and it keeps the current value of "notifyWhen" + } else if (throttle === NOTIFICATION_THROTTLE_RULE) { + return 'onActiveAlert'; + } else { + return 'onThrottleInterval'; + } +}; + +/** + * Given a throttle from a "security_solution" rule this will transform it into an "alerting" "throttle" + * on their saved object. + * @params throttle The throttle from a "security_solution" rule + * @returns The "alerting" throttle + */ +export const transformToAlertThrottle = (throttle: string | null | undefined): string | null => { + if ( + throttle == null || + throttle === NOTIFICATION_THROTTLE_RULE || + throttle === NOTIFICATION_THROTTLE_NO_ACTIONS + ) { + return null; + } else { + return throttle; + } +}; + +/** + * Given a throttle from an "alerting" Saved Object (SO) this will transform it into a "security_solution" + * throttle type. If given the "legacyRuleActions" but we detect that the rule for an unknown reason has actions + * on it to which should not be typical but possible due to the split nature of the API's, this will prefer the + * usage of the non-legacy version. Eventually the "legacyRuleActions" should be removed. + * @param throttle The throttle from a "alerting" Saved Object (SO) + * @param legacyRuleActions Legacy "side car" rule actions that if it detects it being passed it in will transform using it. + * @returns The "security_solution" throttle + */ +export const transformFromAlertThrottle = ( + rule: RuleAlertType, + legacyRuleActions: LegacyRuleActions | null | undefined +): string => { + if (legacyRuleActions == null || (rule.actions != null && rule.actions.length > 0)) { + if (rule.muteAll || rule.actions.length === 0) { + return NOTIFICATION_THROTTLE_NO_ACTIONS; + } else if ( + rule.notifyWhen === 'onActiveAlert' || + (rule.throttle == null && rule.notifyWhen == null) + ) { + return NOTIFICATION_THROTTLE_RULE; + } else if (rule.throttle == null) { + return NOTIFICATION_THROTTLE_NO_ACTIONS; + } else { + return rule.throttle; + } + } else { + return legacyRuleActions.ruleThrottle; + } +}; + +/** + * Given a set of actions from an "alerting" Saved Object (SO) this will transform it into a "security_solution" alert action. + * If this detects any legacy rule actions it will transform it. If both are sent in which is not typical but possible due to + * the split nature of the API's this will prefer the usage of the non-legacy version. Eventually the "legacyRuleActions" should + * be removed. + * @param alertAction The alert action form a "alerting" Saved Object (SO). + * @param legacyRuleActions Legacy "side car" rule actions that if it detects it being passed it in will transform using it. + * @returns The actions of the RuleResponse + */ +export const transformActions = ( + alertAction: RuleAction[] | undefined, + legacyRuleActions: LegacyRuleActions | null | undefined +): RuleResponse['actions'] => { + if (alertAction != null && alertAction.length !== 0) { + return alertAction.map((action) => transformAlertToRuleAction(action)); + } else if (legacyRuleActions != null) { + return legacyRuleActions.actions; + } else { + return []; + } +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.test.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.test.ts index 50cfce7ac905a..f5e17d2bff231 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.test.ts @@ -14,8 +14,8 @@ import { getSavedQueryRuleParams, getThreatRuleParams, getThresholdRuleParams, -} from './rule_schemas.mock'; -import { getRuleMock } from '../routes/__mocks__/request_responses'; +} from '../../rule_schema/mocks'; +import { getRuleMock } from '../../routes/__mocks__/request_responses'; describe('rule_converters', () => { describe('patchTypeSpecificSnakeToCamel', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts similarity index 92% rename from x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts index e25f387160e5b..82d6ee6b1c4b2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts @@ -8,14 +8,53 @@ import uuid from 'uuid'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; -import { ruleTypeMappings } from '@kbn/securitysolution-rules'; import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; - +import { ruleTypeMappings } from '@kbn/securitysolution-rules'; import type { ResolvedSanitizedRule, SanitizedRule } from '@kbn/alerting-plugin/common'; + +import { + DEFAULT_INDICATOR_SOURCE_PATH, + DEFAULT_MAX_SIGNALS, + SERVER_APP_ID, +} from '../../../../../common/constants'; + +import type { PatchRuleRequestBody } from '../../../../../common/detection_engine/rule_management'; +import type { RuleExecutionSummary } from '../../../../../common/detection_engine/rule_monitoring'; +import type { + RelatedIntegrationArray, + RequiredFieldArray, + SetupGuide, + RuleCreateProps, + RuleResponse, + TypeSpecificCreateProps, + TypeSpecificResponse, +} from '../../../../../common/detection_engine/rule_schema'; +import { + EqlPatchParams, + MachineLearningPatchParams, + NewTermsPatchParams, + QueryPatchParams, + SavedQueryPatchParams, + ThreatMatchPatchParams, + ThresholdPatchParams, +} from '../../../../../common/detection_engine/rule_schema'; + +import { + transformAlertToRuleResponseAction, + transformRuleToAlertAction, + transformRuleToAlertResponseAction, +} from '../../../../../common/detection_engine/transform_actions'; + import { normalizeMachineLearningJobIds, normalizeThresholdObject, -} from '../../../../common/detection_engine/utils'; +} from '../../../../../common/detection_engine/utils'; + +import { assertUnreachable } from '../../../../../common/utility_types'; + +// eslint-disable-next-line no-restricted-imports +import type { LegacyRuleActions } from '../../rule_actions_legacy'; +import { mergeRuleExecutionSummary } from '../../rule_monitoring'; import type { InternalRuleCreate, RuleParams, @@ -36,56 +75,13 @@ import type { InternalRuleUpdate, NewTermsRuleParams, NewTermsSpecificRuleParams, -} from './rule_schemas'; -import { assertUnreachable } from '../../../../common/utility_types'; -import type { - RelatedIntegrationArray, - RequiredFieldArray, - SetupGuide, -} from '../../../../common/detection_engine/schemas/common'; -import type { RuleExecutionSummary } from '../../../../common/detection_engine/rule_monitoring'; -import { - eqlPatchParams, - machineLearningPatchParams, - newTermsPatchParams, - queryPatchParams, - savedQueryPatchParams, - threatMatchPatchParams, - thresholdPatchParams, -} from '../../../../common/detection_engine/schemas/request'; -import type { - CreateRulesSchema, - CreateTypeSpecific, - EqlPatchParams, - FullResponseSchema, - MachineLearningPatchParams, - NewTermsPatchParams, - QueryPatchParams, - ResponseTypeSpecific, - SavedQueryPatchParams, - ThreatMatchPatchParams, - ThresholdPatchParams, -} from '../../../../common/detection_engine/schemas/request'; -import type { PatchRulesSchema } from '../../../../common/detection_engine/schemas/request/patch_rules_schema'; -import { - DEFAULT_INDICATOR_SOURCE_PATH, - DEFAULT_MAX_SIGNALS, - SERVER_APP_ID, -} from '../../../../common/constants'; -import { - transformAlertToRuleResponseAction, - transformRuleToAlertAction, - transformRuleToAlertResponseAction, -} from '../../../../common/detection_engine/transform_actions'; +} from '../../rule_schema'; import { + transformActions, transformFromAlertThrottle, transformToAlertThrottle, transformToNotifyWhen, - transformActions, -} from '../rules/utils'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyRuleActions } from '../rule_actions/legacy_types'; -import { mergeRuleExecutionSummary } from '../rule_monitoring'; +} from './rule_actions'; // These functions provide conversions from the request API schema to the internal rule schema and from the internal rule schema // to the response API schema. This provides static type-check assurances that the internal schema is in sync with the API schema for @@ -95,7 +91,9 @@ import { mergeRuleExecutionSummary } from '../rule_monitoring'; // Converts params from the snake case API format to the internal camel case format AND applies default values where needed. // Notice that params.language is possibly undefined for most rule types in the API but we default it to kuery to match // the legacy API behavior -export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecificRuleParams => { +export const typeSpecificSnakeToCamel = ( + params: TypeSpecificCreateProps +): TypeSpecificRuleParams => { switch (params.type) { case 'eql': { return { @@ -322,7 +320,7 @@ const parseValidationError = (error: string | null): BadRequestError => { }; export const patchTypeSpecificSnakeToCamel = ( - params: PatchRulesSchema, + params: PatchRuleRequestBody, existingRule: RuleParams ): TypeSpecificRuleParams => { // Here we do the validation of patch params by rule type to ensure that the fields that are @@ -332,49 +330,49 @@ export const patchTypeSpecificSnakeToCamel = ( // but would be assignable to the other rule types since they don't specify `event_category_override`. switch (existingRule.type) { case 'eql': { - const [validated, error] = validateNonExact(params, eqlPatchParams); + const [validated, error] = validateNonExact(params, EqlPatchParams); if (validated == null) { throw parseValidationError(error); } return patchEqlParams(validated, existingRule); } case 'threat_match': { - const [validated, error] = validateNonExact(params, threatMatchPatchParams); + const [validated, error] = validateNonExact(params, ThreatMatchPatchParams); if (validated == null) { throw parseValidationError(error); } return patchThreatMatchParams(validated, existingRule); } case 'query': { - const [validated, error] = validateNonExact(params, queryPatchParams); + const [validated, error] = validateNonExact(params, QueryPatchParams); if (validated == null) { throw parseValidationError(error); } return patchQueryParams(validated, existingRule); } case 'saved_query': { - const [validated, error] = validateNonExact(params, savedQueryPatchParams); + const [validated, error] = validateNonExact(params, SavedQueryPatchParams); if (validated == null) { throw parseValidationError(error); } return patchSavedQueryParams(validated, existingRule); } case 'threshold': { - const [validated, error] = validateNonExact(params, thresholdPatchParams); + const [validated, error] = validateNonExact(params, ThresholdPatchParams); if (validated == null) { throw parseValidationError(error); } return patchThresholdParams(validated, existingRule); } case 'machine_learning': { - const [validated, error] = validateNonExact(params, machineLearningPatchParams); + const [validated, error] = validateNonExact(params, MachineLearningPatchParams); if (validated == null) { throw parseValidationError(error); } return patchMachineLearningParams(validated, existingRule); } case 'new_terms': { - const [validated, error] = validateNonExact(params, newTermsPatchParams); + const [validated, error] = validateNonExact(params, NewTermsPatchParams); if (validated == null) { throw parseValidationError(error); } @@ -387,7 +385,7 @@ export const patchTypeSpecificSnakeToCamel = ( }; const versionExcludedKeys = ['enabled', 'id', 'rule_id']; -const incrementVersion = (nextParams: PatchRulesSchema, existingRule: RuleParams) => { +const incrementVersion = (nextParams: PatchRuleRequestBody, existingRule: RuleParams) => { // The the version from nextParams if it's provided if (nextParams.version) { return nextParams.version; @@ -409,7 +407,7 @@ const incrementVersion = (nextParams: PatchRulesSchema, existingRule: RuleParams // eslint-disable-next-line complexity export const convertPatchAPIToInternalSchema = ( - nextParams: PatchRulesSchema & { + nextParams: PatchRuleRequestBody & { related_integrations?: RelatedIntegrationArray; required_fields?: RequiredFieldArray; setup?: SetupGuide; @@ -473,7 +471,7 @@ export const convertPatchAPIToInternalSchema = ( // eslint-disable-next-line complexity export const convertCreateAPIToInternalSchema = ( - input: CreateRulesSchema & { + input: RuleCreateProps & { related_integrations?: RelatedIntegrationArray; required_fields?: RequiredFieldArray; setup?: SetupGuide; @@ -530,7 +528,7 @@ export const convertCreateAPIToInternalSchema = ( }; // Converts the internal rule data structure to the response API schema -export const typeSpecificCamelToSnake = (params: TypeSpecificRuleParams): ResponseTypeSpecific => { +export const typeSpecificCamelToSnake = (params: TypeSpecificRuleParams): TypeSpecificResponse => { switch (params.type) { case 'eql': { return { @@ -666,10 +664,15 @@ export const internalRuleToAPIResponse = ( rule: SanitizedRule<RuleParams> | ResolvedSanitizedRule<RuleParams>, ruleExecutionSummary?: RuleExecutionSummary | null, legacyRuleActions?: LegacyRuleActions | null -): FullResponseSchema => { - const mergedExecutionSummary = mergeRuleExecutionSummary(rule, ruleExecutionSummary ?? null); +): RuleResponse => { + const mergedExecutionSummary = mergeRuleExecutionSummary( + rule.executionStatus, + ruleExecutionSummary ?? null + ); + const isResolvedRule = (obj: unknown): obj is ResolvedSanitizedRule<RuleParams> => (obj as ResolvedSanitizedRule<RuleParams>).outcome != null; + return { // saved object properties outcome: isResolvedRule(rule) ? rule.outcome : undefined, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts similarity index 94% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts index dd9d7ef74cb30..4737fad57a7e9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.test.ts @@ -5,49 +5,47 @@ * 2.0. */ +import { partition } from 'lodash/fp'; import { Readable } from 'stream'; import { createPromiseFromStreams } from '@kbn/utils'; -import type { Action, ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { RuleAction, ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { PartialRule } from '@kbn/alerting-plugin/server'; + +import type { RuleToImport } from '../../../../../common/detection_engine/rule_management'; +import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/rule_schema/mocks'; +import { requestContextMock } from '../../routes/__mocks__'; +import { getOutputRuleAlertForRest } from '../../routes/__mocks__/utils'; import { getIdError, transformFindAlerts, transform, getIdBulkError, transformAlertsToRules, - getDuplicates, getTupleDuplicateErrorsAndUniqueRules, getInvalidConnectors, swapActionIds, migrateLegacyActionsIds, } from './utils'; -import { getRuleMock } from '../__mocks__/request_responses'; +import { getRuleMock } from '../../routes/__mocks__/request_responses'; import type { PartialFilter } from '../../types'; -import type { BulkError } from '../utils'; -import { createBulkErrorObject } from '../utils'; -import { getOutputRuleAlertForRest } from '../__mocks__/utils'; -import type { PartialRule } from '@kbn/alerting-plugin/server'; -import { createRulesAndExceptionsStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; -import type { RuleAlertType } from '../../rules/types'; -import type { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; -import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; -import type { CreateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request'; -import { - getMlRuleParams, - getQueryRuleParams, - getThreatRuleParams, -} from '../../schemas/rule_schemas.mock'; -import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; -import { requestContextMock } from '../__mocks__'; +import type { BulkError } from '../../routes/utils'; +import { createBulkErrorObject } from '../../routes/utils'; + +import type { RuleAlertType } from '../../rule_schema'; +import { getMlRuleParams, getQueryRuleParams, getThreatRuleParams } from '../../rule_schema/mocks'; // eslint-disable-next-line no-restricted-imports -import type { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; -// eslint-disable-next-line no-restricted-imports -import type { LegacyRuleAlertAction } from '../../rule_actions/legacy_types'; -import type { RuleExceptionsPromiseFromStreams } from './utils/import_rules_utils'; -import { partition } from 'lodash/fp'; +import type { + LegacyRuleAlertAction, + LegacyRulesActionsSavedObject, +} from '../../rule_actions_legacy'; + +import { createRulesAndExceptionsStreamFromNdJson } from '../logic/import/create_rules_stream_from_ndjson'; +import type { RuleExceptionsPromiseFromStreams } from '../logic/import/import_rules_utils'; +import { internalRuleToAPIResponse } from '../normalization/rule_converters'; -type PromiseFromStreams = ImportRulesSchema | Error; +type PromiseFromStreams = RuleToImport | Error; const createMockImportRule = async (rule: ReturnType<typeof getCreateRulesSchemaMock>) => { const ndJsonStream = new Readable({ @@ -492,39 +490,6 @@ describe('utils', () => { }); }); - describe('getDuplicates', () => { - test("returns array of ruleIds showing the duplicate keys of 'value2' and 'value3'", () => { - const output = getDuplicates( - [ - { rule_id: 'value1' }, - { rule_id: 'value2' }, - { rule_id: 'value2' }, - { rule_id: 'value3' }, - { rule_id: 'value3' }, - {}, - {}, - ] as CreateRulesBulkSchema, - 'rule_id' - ); - const expected = ['value2', 'value3']; - expect(output).toEqual(expected); - }); - test('returns null when given a map of no duplicates', () => { - const output = getDuplicates( - [ - { rule_id: 'value1' }, - { rule_id: 'value2' }, - { rule_id: 'value3' }, - {}, - {}, - ] as CreateRulesBulkSchema, - 'rule_id' - ); - const expected: string[] = []; - expect(output).toEqual(expected); - }); - }); - describe('getTupleDuplicateErrorsAndUniqueRules', () => { test('returns tuple of empty duplicate errors array and rule array with instance of Syntax Error when imported rule contains parse error', async () => { // This is a string because we have a double "::" below to make an error happen on purpose. @@ -645,7 +610,7 @@ describe('utils', () => { }); describe('swapActionIds', () => { - const mockAction: Action = { + const mockAction: RuleAction = { group: 'group string', id: 'some-7.x-id', action_type_id: '.slack', @@ -706,7 +671,7 @@ describe('utils', () => { }); describe('migrateLegacyActionsIds', () => { - const mockAction: Action = { + const mockAction: RuleAction = { group: 'group string', id: 'some-7.x-id', action_type_id: '.slack', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts similarity index 85% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts index 9dfd5b1efed7c..3c8ca41801303 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/utils.ts @@ -5,29 +5,29 @@ * 2.0. */ -import { countBy, partition } from 'lodash/fp'; -import uuid from 'uuid'; -import type { Action } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { SavedObjectsClientContract } from '@kbn/core/server'; +import { partition } from 'lodash/fp'; import pMap from 'p-map'; +import uuid from 'uuid'; +import type { SavedObjectsClientContract } from '@kbn/core/server'; +import type { RuleAction } from '@kbn/securitysolution-io-ts-alerting-types'; import type { PartialRule, FindResult } from '@kbn/alerting-plugin/server'; import type { ActionsClient, FindActionResult } from '@kbn/actions-plugin/server'; + +import type { RuleToImport } from '../../../../../common/detection_engine/rule_management'; import type { RuleExecutionSummary } from '../../../../../common/detection_engine/rule_monitoring'; -import type { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; -import type { CreateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; -import type { RuleAlertType } from '../../rules/types'; -import { isAlertType } from '../../rules/types'; -import type { BulkError, OutputError } from '../utils'; -import { createBulkErrorObject } from '../utils'; -import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; -import type { RuleParams } from '../../schemas/rule_schemas'; +import type { RuleResponse } from '../../../../../common/detection_engine/rule_schema'; + // eslint-disable-next-line no-restricted-imports -import type { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; +import type { LegacyRulesActionsSavedObject } from '../../rule_actions_legacy'; import type { RuleExecutionSummariesByRuleId } from '../../rule_monitoring'; -import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; +import type { RuleAlertType, RuleParams } from '../../rule_schema'; +import { isAlertType } from '../../rule_schema'; +import type { BulkError, OutputError } from '../../routes/utils'; +import { createBulkErrorObject } from '../../routes/utils'; +import { internalRuleToAPIResponse } from '../normalization/rule_converters'; -type PromiseFromStreams = ImportRulesSchema | Error; +type PromiseFromStreams = RuleToImport | Error; const MAX_CONCURRENT_SEARCHES = 10; export const getIdError = ({ @@ -92,7 +92,7 @@ export const getIdBulkError = ({ export const transformAlertsToRules = ( rules: RuleAlertType[], legacyRuleActions: Record<string, LegacyRulesActionsSavedObject> -): FullResponseSchema[] => { +): RuleResponse[] => { return rules.map((rule) => internalRuleToAPIResponse(rule, null, legacyRuleActions[rule.id])); }; @@ -104,7 +104,7 @@ export const transformFindAlerts = ( page: number; perPage: number; total: number; - data: Array<Partial<FullResponseSchema>>; + data: Array<Partial<RuleResponse>>; } | null => { return { page: ruleFindResults.page, @@ -121,7 +121,7 @@ export const transform = ( rule: PartialRule<RuleParams>, ruleExecutionSummary?: RuleExecutionSummary | null, legacyRuleActions?: LegacyRulesActionsSavedObject | null -): FullResponseSchema | null => { +): RuleResponse | null => { if (isAlertType(rule)) { return internalRuleToAPIResponse(rule, ruleExecutionSummary, legacyRuleActions); } @@ -129,18 +129,6 @@ export const transform = ( return null; }; -export const getDuplicates = (ruleDefinitions: CreateRulesBulkSchema, by: 'rule_id'): string[] => { - const mappedDuplicates = countBy( - by, - ruleDefinitions.filter((r) => r[by] != null) - ); - const hasDuplicates = Object.values(mappedDuplicates).some((i) => i > 1); - if (hasDuplicates) { - return Object.keys(mappedDuplicates).filter((key) => mappedDuplicates[key] > 1); - } - return []; -}; - export const getTupleDuplicateErrorsAndUniqueRules = ( rules: PromiseFromStreams[], isOverwrite: boolean @@ -189,12 +177,12 @@ const createQuery = (type: string, id: string) => * @returns */ export const swapActionIds = async ( - action: Action, + action: RuleAction, savedObjectsClient: SavedObjectsClientContract -): Promise<Action | Error> => { +): Promise<RuleAction | Error> => { try { const search = createQuery('action', action.id); - const foundAction = await savedObjectsClient.find<Action>({ + const foundAction = await savedObjectsClient.find<RuleAction>({ type: 'action', search, rootSearchFields: ['_id', 'originId'], @@ -247,7 +235,7 @@ export const migrateLegacyActionsIds = async ( rules: PromiseFromStreams[], savedObjectsClient: SavedObjectsClientContract ): Promise<PromiseFromStreams[]> => { - const isImportRule = (r: unknown): r is ImportRulesSchema => !(r instanceof Error); + const isImportRule = (r: unknown): r is RuleToImport => !(r instanceof Error); const toReturn = await pMap( rules, @@ -255,14 +243,14 @@ export const migrateLegacyActionsIds = async ( if (isImportRule(rule)) { // can we swap the pre 8.0 action connector(s) id with the new, // post-8.0 action id (swap the originId for the new _id?) - const newActions: Array<Action | Error> = await pMap( + const newActions: Array<RuleAction | Error> = await pMap( rule.actions ?? [], - (action: Action) => swapActionIds(action, savedObjectsClient), + (action: RuleAction) => swapActionIds(action, savedObjectsClient), { concurrency: MAX_CONCURRENT_SEARCHES } ); // were there any errors discovered while trying to migrate and swap the action connector ids? - const [actionMigrationErrors, newlyMigratedActions] = partition<Action | Error, Error>( + const [actionMigrationErrors, newlyMigratedActions] = partition<RuleAction | Error, Error>( (item): item is Error => item instanceof Error )(newActions); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts similarity index 92% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts index fbd763aed9758..8d920ef4ba652 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts @@ -6,15 +6,15 @@ */ import { transformValidate, transformValidateBulkError } from './validate'; -import type { BulkError } from '../utils'; -import { getRuleMock } from '../__mocks__/request_responses'; +import type { BulkError } from '../../routes/utils'; +import { getRuleMock } from '../../routes/__mocks__/request_responses'; import { ruleExecutionSummaryMock } from '../../../../../common/detection_engine/rule_monitoring/mocks'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; -import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; +import { getQueryRuleParams } from '../../rule_schema/mocks'; +import type { RuleResponse } from '../../../../../common/detection_engine/rule_schema'; -export const ruleOutput = (): FullResponseSchema => ({ +export const ruleOutput = (): RuleResponse => ({ actions: [], author: ['Elastic'], building_block_type: 'default', @@ -124,7 +124,7 @@ describe('validate', () => { const rule = getRuleMock(getQueryRuleParams()); const ruleExecutionSumary = ruleExecutionSummaryMock.getSummarySucceeded(); const validatedOrError = transformValidateBulkError('rule-1', rule, ruleExecutionSumary); - const expected: FullResponseSchema = { + const expected: RuleResponse = { ...ruleOutput(), execution_summary: ruleExecutionSumary, }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts similarity index 68% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts index 42e50db79294a..e784068e0248e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.ts @@ -9,27 +9,26 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import type { PartialRule } from '@kbn/alerting-plugin/server'; import type { RuleExecutionSummary } from '../../../../../common/detection_engine/rule_monitoring'; -import type { FullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; -import { fullResponseSchema } from '../../../../../common/detection_engine/schemas/request'; -import { isAlertType } from '../../rules/types'; -import type { BulkError } from '../utils'; -import { createBulkErrorObject } from '../utils'; +import { RuleResponse } from '../../../../../common/detection_engine/rule_schema'; +import type { RuleParams } from '../../rule_schema'; +import { isAlertType } from '../../rule_schema'; +import type { BulkError } from '../../routes/utils'; +import { createBulkErrorObject } from '../../routes/utils'; import { transform } from './utils'; -import type { RuleParams } from '../../schemas/rule_schemas'; // eslint-disable-next-line no-restricted-imports -import type { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; -import { internalRuleToAPIResponse } from '../../schemas/rule_converters'; +import type { LegacyRulesActionsSavedObject } from '../../rule_actions_legacy'; +import { internalRuleToAPIResponse } from '../normalization/rule_converters'; export const transformValidate = ( rule: PartialRule<RuleParams>, ruleExecutionSummary: RuleExecutionSummary | null, legacyRuleActions?: LegacyRulesActionsSavedObject | null -): [FullResponseSchema | null, string | null] => { +): [RuleResponse | null, string | null] => { const transformed = transform(rule, ruleExecutionSummary, legacyRuleActions); if (transformed == null) { return [null, 'Internal error transforming']; } else { - return validateNonExact(transformed, fullResponseSchema); + return validateNonExact(transformed, RuleResponse); } }; @@ -37,10 +36,10 @@ export const transformValidateBulkError = ( ruleId: string, rule: PartialRule<RuleParams>, ruleExecutionSummary: RuleExecutionSummary | null -): FullResponseSchema | BulkError => { +): RuleResponse | BulkError => { if (isAlertType(rule)) { const transformed = internalRuleToAPIResponse(rule, ruleExecutionSummary); - const [validated, errors] = validateNonExact(transformed, fullResponseSchema); + const [validated, errors] = validateNonExact(transformed, RuleResponse); if (errors != null || validated == null) { return createBulkErrorObject({ ruleId, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/merge_rule_execution_summary.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/merge_rule_execution_summary.ts index 2b017d27bb971..cf403f3d6b66c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/merge_rule_execution_summary.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/logic/rule_execution_log/merge_rule_execution_summary.ts @@ -5,22 +5,23 @@ * 2.0. */ +import type { RuleExecutionStatus as RuleExecutionStatusByFramework } from '@kbn/alerting-plugin/common'; + import type { RuleExecutionSummary } from '../../../../../../common/detection_engine/rule_monitoring'; import { RuleExecutionStatus, ruleExecutionStatusToNumber, } from '../../../../../../common/detection_engine/rule_monitoring'; -import type { RuleAlertType } from '../../../rules/types'; export const mergeRuleExecutionSummary = ( - rule: RuleAlertType, + ruleExecutionStatus: RuleExecutionStatusByFramework, ruleExecutionSummary: RuleExecutionSummary | null ): RuleExecutionSummary | null => { if (ruleExecutionSummary == null) { return null; } - const frameworkStatus = rule.executionStatus; + const frameworkStatus = ruleExecutionStatus; const customStatus = ruleExecutionSummary.last_execution; if ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts similarity index 88% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts index 9a1ba3a2b144c..1c82dc1194cef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts @@ -17,30 +17,35 @@ import type { import { parseDuration } from '@kbn/alerting-plugin/common'; import type { ExecutorType } from '@kbn/alerting-plugin/server/types'; import type { Alert } from '@kbn/alerting-plugin/server'; -import type { StartPlugins, SetupPlugins } from '../../../../plugin'; -import { buildSiemResponse } from '../utils'; -import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters'; -import type { RuleParams } from '../../schemas/rule_schemas'; -import { createPreviewRuleExecutionLogger } from '../../signals/preview/preview_rule_execution_logger'; -import { parseInterval } from '../../signals/utils'; -import { buildMlAuthz } from '../../../machine_learning/authz'; -import { throwAuthzError } from '../../../machine_learning/validation'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { createRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/create_rules_type_dependents'; + import { DEFAULT_PREVIEW_INDEX, DETECTION_ENGINE_RULES_PREVIEW, -} from '../../../../../common/constants'; -import { wrapScopedClusterClient } from './utils/wrap_scoped_cluster_client'; -import type { RulePreviewLogs } from '../../../../../common/detection_engine/schemas/request'; -import { previewRulesSchema } from '../../../../../common/detection_engine/schemas/request'; -import { RuleExecutionStatus } from '../../../../../common/detection_engine/rule_monitoring'; -import type { RuleExecutionContext, StatusChangeArgs } from '../../rule_monitoring'; +} from '../../../../../../common/constants'; +import { validateCreateRuleProps } from '../../../../../../common/detection_engine/rule_management'; +import { RuleExecutionStatus } from '../../../../../../common/detection_engine/rule_monitoring'; +import type { RulePreviewLogs } from '../../../../../../common/detection_engine/rule_schema'; +import { previewRulesSchema } from '../../../../../../common/detection_engine/rule_schema'; + +import type { StartPlugins, SetupPlugins } from '../../../../../plugin'; +import { buildSiemResponse } from '../../../routes/utils'; +import { convertCreateAPIToInternalSchema } from '../../../rule_management'; +import type { RuleParams } from '../../../rule_schema'; +import { createPreviewRuleExecutionLogger } from '../../../signals/preview/preview_rule_execution_logger'; +import { parseInterval } from '../../../signals/utils'; +import { buildMlAuthz } from '../../../../machine_learning/authz'; +import { throwAuthzError } from '../../../../machine_learning/validation'; +import { buildRouteValidation } from '../../../../../utils/build_validation/route_validation'; +import type { SecuritySolutionPluginRouter } from '../../../../../types'; -import type { ConfigType } from '../../../../config'; -import { alertInstanceFactoryStub } from '../../signals/preview/alert_instance_factory_stub'; -import type { CreateRuleOptions, CreateSecurityRuleTypeWrapperProps } from '../../rule_types/types'; +import type { RuleExecutionContext, StatusChangeArgs } from '../../../rule_monitoring'; + +import type { ConfigType } from '../../../../../config'; +import { alertInstanceFactoryStub } from '../../../signals/preview/alert_instance_factory_stub'; +import type { + CreateRuleOptions, + CreateSecurityRuleTypeWrapperProps, +} from '../../../rule_types/types'; import { createEqlAlertType, createIndicatorMatchAlertType, @@ -49,10 +54,11 @@ import { createSavedQueryAlertType, createThresholdAlertType, createNewTermsAlertType, -} from '../../rule_types'; -import { createSecurityRuleTypeWrapper } from '../../rule_types/create_security_rule_type_wrapper'; -import { assertUnreachable } from '../../../../../common/utility_types'; -import { wrapSearchSourceClient } from './utils/wrap_search_source_client'; +} from '../../../rule_types'; +import { createSecurityRuleTypeWrapper } from '../../../rule_types/create_security_rule_type_wrapper'; +import { assertUnreachable } from '../../../../../../common/utility_types'; +import { wrapScopedClusterClient } from './wrap_scoped_cluster_client'; +import { wrapSearchSourceClient } from './wrap_search_source_client'; const PREVIEW_TIMEOUT_SECONDS = 60; @@ -79,7 +85,7 @@ export const previewRulesRoute = async ( }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); - const validationErrors = createRuleValidateTypeDependents(request.body); + const validationErrors = validateCreateRuleProps(request.body); const coreContext = await context.core; if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_scoped_cluster_client.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.test.ts similarity index 99% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_scoped_cluster_client.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.test.ts index 551562c6b6e9f..88410e8db204d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_scoped_cluster_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.test.ts @@ -15,7 +15,7 @@ const esQuery = { describe('wrapScopedClusterClient', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_scoped_cluster_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_scoped_cluster_client.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts similarity index 99% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts index 371e49d26db96..4376b8785e0e3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts @@ -25,7 +25,7 @@ const createSearchSourceClientMock = () => { describe('wrapSearchSourceClient', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/wrap_search_source_client.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/register_routes.ts new file mode 100644 index 0000000000000..4fedc1ea27849 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/register_routes.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 { Logger, StartServicesAccessor } from '@kbn/core/server'; +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; + +import type { ConfigType } from '../../../../config'; +import type { SetupPlugins, StartPlugins } from '../../../../plugin_contract'; +import type { SecuritySolutionPluginRouter } from '../../../../types'; +import type { CreateRuleOptions, CreateSecurityRuleTypeWrapperProps } from '../../rule_types/types'; + +import { previewRulesRoute } from './preview_rules/route'; + +export const registerRulePreviewRoutes = ( + router: SecuritySolutionPluginRouter, + config: ConfigType, + ml: SetupPlugins['ml'], + security: SetupPlugins['security'], + ruleOptions: CreateRuleOptions, + securityRuleTypeOptions: CreateSecurityRuleTypeWrapperProps, + previewRuleDataClient: IRuleDataClient, + getStartServices: StartServicesAccessor<StartPlugins>, + logger: Logger +) => { + previewRulesRoute( + router, + config, + ml, + security, + ruleOptions, + securityRuleTypeOptions, + previewRuleDataClient, + getStartServices, + logger + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/index.ts new file mode 100644 index 0000000000000..c0123e587d9bf --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/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 * from './api/register_routes'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/index.ts new file mode 100644 index 0000000000000..9b4598ed13641 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/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 * from './model/rule_alert_type'; +export * from './model/rule_schemas'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/mocks.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/mocks.ts new file mode 100644 index 0000000000000..7b2a4f4440689 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/mocks.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 * from './model/rule_schemas.mock'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_alert_type.ts new file mode 100644 index 0000000000000..f7e73b49104c1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_alert_type.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. + */ + +import { ruleTypeMappings } from '@kbn/securitysolution-rules'; +import type { SanitizedRule } from '@kbn/alerting-plugin/common'; +import type { PartialRule } from '@kbn/alerting-plugin/server'; +import type { RuleParams } from './rule_schemas'; + +export type RuleAlertType = SanitizedRule<RuleParams>; + +export const isAlertType = ( + partialAlert: PartialRule<RuleParams> +): partialAlert is RuleAlertType => { + const ruleTypeValues = Object.values(ruleTypeMappings) as unknown as string[]; + return ruleTypeValues.includes(partialAlert.alertTypeId as string); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts similarity index 93% rename from x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts index d608c08eabcd9..f41494cf5fed4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { getThreatMock } from '../../../../common/detection_engine/schemas/types/threat.mock'; -import { getListArrayMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; -import { getThreatMappingMock } from '../signals/threat_mapping/build_threat_mapping_filter.mock'; +import { getThreatMock } from '../../../../../common/detection_engine/schemas/types/threat.mock'; +import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; +import { getThreatMappingMock } from '../../signals/threat_mapping/build_threat_mapping_filter.mock'; import type { BaseRuleParams, CompleteRule, @@ -19,9 +19,9 @@ import type { SavedQueryRuleParams, ThreatRuleParams, ThresholdRuleParams, -} from './rule_schemas'; +} from '..'; import type { SanitizedRuleConfig } from '@kbn/alerting-plugin/common'; -import { sampleRuleGuid } from '../signals/__mocks__/es_results'; +import { sampleRuleGuid } from '../../signals/__mocks__/es_results'; const getBaseRuleParams = (): BaseRuleParams => { return { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts similarity index 62% rename from x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts index 80f9adf29aacb..0e64cc3788a12 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts @@ -8,25 +8,22 @@ import * as t from 'io-ts'; import { - actionsCamel, - from, + concurrentSearchesOrUndefined, + itemsPerSearchOrUndefined, machine_learning_job_id_normalized, - risk_score, - risk_score_mapping, - threat_mapping, + RiskScore, + RiskScoreMapping, + RuleActionArrayCamel, + RuleActionThrottle, + RuleIntervalFrom, + RuleIntervalTo, + Severity, + SeverityMapping, threat_index, + threat_mapping, threat_query, - concurrentSearchesOrUndefined, - itemsPerSearchOrUndefined, threatIndicatorPathOrUndefined, - threats, - severity, - severity_mapping, - throttleOrNull, - max_signals, } from '@kbn/securitysolution-io-ts-alerting-types'; -import { listArray } from '@kbn/securitysolution-io-ts-list-types'; -import { version } from '@kbn/securitysolution-io-ts-types'; import { SIGNALS_ID, EQL_RULE_TYPE_ID, @@ -40,80 +37,84 @@ import { import type { SanitizedRuleConfig } from '@kbn/alerting-plugin/common'; import { - author, - buildingBlockTypeOrUndefined, - description, - enabled, - namespaceOrUndefined, - noteOrUndefined, - false_positives, - rule_id, - immutable, - dataViewIdOrUndefined, - indexOrUndefined, - licenseOrUndefined, - output_index, - timelineIdOrUndefined, - timelineTitleOrUndefined, - metaOrUndefined, - name, - query, - queryOrUndefined, - filtersOrUndefined, - ruleNameOverrideOrUndefined, - tags, - timestampOverrideOrUndefined, - to, - references, - timestampFieldOrUndefined, - eventCategoryOverrideOrUndefined, - tiebreakerFieldOrUndefined, - savedIdOrUndefined, - saved_id, - thresholdNormalized, - anomaly_threshold, + AlertsIndex, + AlertsIndexNamespace, + BuildingBlockType, + DataViewId, + EventCategoryOverride, + ExceptionListArray, + HistoryWindowStart, + IndexPatternArray, + InvestigationGuide, + IsRuleEnabled, + IsRuleImmutable, + MaxSignals, + NewTermsFields, RelatedIntegrationArray, RequiredFieldArray, + RuleAuthorArray, + RuleDescription, + RuleFalsePositiveArray, + RuleFilterArray, + RuleLicense, + RuleMetadata, + RuleName, + RuleNameOverride, + RuleQuery, + RuleReferenceArray, + RuleSignatureId, + RuleTagArray, + RuleVersion, SetupGuide, - newTermsFields, - historyWindowStart, - timestampOverrideFallbackDisabledOrUndefined, -} from '../../../../common/detection_engine/schemas/common'; -import { SERVER_APP_ID } from '../../../../common/constants'; -import { ResponseActionRuleParamsOrUndefined } from '../../../../common/detection_engine/rule_response_actions/schemas'; + ThreatArray, + ThresholdNormalized, + TiebreakerField, + TimelineTemplateId, + TimelineTemplateTitle, + TimestampField, + TimestampOverride, + TimestampOverrideFallbackDisabled, +} from '../../../../../common/detection_engine/rule_schema'; +import { + savedIdOrUndefined, + saved_id, + anomaly_threshold, +} from '../../../../../common/detection_engine/schemas/common'; +import { SERVER_APP_ID } from '../../../../../common/constants'; +import { ResponseActionRuleParamsOrUndefined } from '../../../../../common/detection_engine/rule_response_actions/schemas'; const nonEqlLanguages = t.keyof({ kuery: null, lucene: null }); export const baseRuleParams = t.exact( t.type({ - author, - buildingBlockType: buildingBlockTypeOrUndefined, - description, - namespace: namespaceOrUndefined, - note: noteOrUndefined, - falsePositives: false_positives, - from, - ruleId: rule_id, - immutable, - license: licenseOrUndefined, - outputIndex: output_index, - timelineId: timelineIdOrUndefined, - timelineTitle: timelineTitleOrUndefined, - meta: metaOrUndefined, + author: RuleAuthorArray, + buildingBlockType: t.union([BuildingBlockType, t.undefined]), + description: RuleDescription, + namespace: t.union([AlertsIndexNamespace, t.undefined]), + note: t.union([InvestigationGuide, t.undefined]), + falsePositives: RuleFalsePositiveArray, + from: RuleIntervalFrom, + ruleId: RuleSignatureId, + immutable: IsRuleImmutable, + license: t.union([RuleLicense, t.undefined]), + outputIndex: AlertsIndex, + timelineId: t.union([TimelineTemplateId, t.undefined]), + timelineTitle: t.union([TimelineTemplateTitle, t.undefined]), + meta: t.union([RuleMetadata, t.undefined]), // maxSignals not used in ML rules but probably should be used - maxSignals: max_signals, - riskScore: risk_score, - riskScoreMapping: risk_score_mapping, - ruleNameOverride: ruleNameOverrideOrUndefined, - severity, - severityMapping: severity_mapping, - timestampOverride: timestampOverrideOrUndefined, - timestampOverrideFallbackDisabled: timestampOverrideFallbackDisabledOrUndefined, - threat: threats, - to, - references, - version, - exceptionsList: listArray, + maxSignals: MaxSignals, + riskScore: RiskScore, + riskScoreMapping: RiskScoreMapping, + ruleNameOverride: t.union([RuleNameOverride, t.undefined]), + severity: Severity, + severityMapping: SeverityMapping, + timestampOverride: t.union([TimestampOverride, t.undefined]), + timestampOverrideFallbackDisabled: t.union([TimestampOverrideFallbackDisabled, t.undefined]), + threat: ThreatArray, + to: RuleIntervalTo, + references: RuleReferenceArray, + version: RuleVersion, + exceptionsList: ExceptionListArray, relatedIntegrations: t.union([RelatedIntegrationArray, t.undefined]), requiredFields: t.union([RequiredFieldArray, t.undefined]), setup: t.union([SetupGuide, t.undefined]), @@ -124,13 +125,13 @@ export type BaseRuleParams = t.TypeOf<typeof baseRuleParams>; const eqlSpecificRuleParams = t.type({ type: t.literal('eql'), language: t.literal('eql'), - index: indexOrUndefined, - query, - filters: filtersOrUndefined, - timestampField: timestampFieldOrUndefined, - eventCategoryOverride: eventCategoryOverrideOrUndefined, - dataViewId: dataViewIdOrUndefined, - tiebreakerField: tiebreakerFieldOrUndefined, + index: t.union([IndexPatternArray, t.undefined]), + dataViewId: t.union([DataViewId, t.undefined]), + query: RuleQuery, + filters: t.union([RuleFilterArray, t.undefined]), + eventCategoryOverride: t.union([EventCategoryOverride, t.undefined]), + timestampField: t.union([TimestampField, t.undefined]), + tiebreakerField: t.union([TiebreakerField, t.undefined]), }); export const eqlRuleParams = t.intersection([baseRuleParams, eqlSpecificRuleParams]); export type EqlSpecificRuleParams = t.TypeOf<typeof eqlSpecificRuleParams>; @@ -139,11 +140,11 @@ export type EqlRuleParams = t.TypeOf<typeof eqlRuleParams>; const threatSpecificRuleParams = t.type({ type: t.literal('threat_match'), language: nonEqlLanguages, - index: indexOrUndefined, - query, - filters: filtersOrUndefined, + index: t.union([IndexPatternArray, t.undefined]), + query: RuleQuery, + filters: t.union([RuleFilterArray, t.undefined]), savedId: savedIdOrUndefined, - threatFilters: filtersOrUndefined, + threatFilters: t.union([RuleFilterArray, t.undefined]), threatQuery: threat_query, threatMapping: threat_mapping, threatLanguage: t.union([nonEqlLanguages, t.undefined]), @@ -151,7 +152,7 @@ const threatSpecificRuleParams = t.type({ threatIndicatorPath: threatIndicatorPathOrUndefined, concurrentSearches: concurrentSearchesOrUndefined, itemsPerSearch: itemsPerSearchOrUndefined, - dataViewId: dataViewIdOrUndefined, + dataViewId: t.union([DataViewId, t.undefined]), }); export const threatRuleParams = t.intersection([baseRuleParams, threatSpecificRuleParams]); export type ThreatSpecificRuleParams = t.TypeOf<typeof threatSpecificRuleParams>; @@ -161,11 +162,11 @@ const querySpecificRuleParams = t.exact( t.type({ type: t.literal('query'), language: nonEqlLanguages, - index: indexOrUndefined, - query, - filters: filtersOrUndefined, + index: t.union([IndexPatternArray, t.undefined]), + query: RuleQuery, + filters: t.union([RuleFilterArray, t.undefined]), savedId: savedIdOrUndefined, - dataViewId: dataViewIdOrUndefined, + dataViewId: t.union([DataViewId, t.undefined]), responseActions: ResponseActionRuleParamsOrUndefined, }) ); @@ -178,10 +179,10 @@ const savedQuerySpecificRuleParams = t.type({ // Having language, query, and filters possibly defined adds more code confusion and probably user confusion // if the saved object gets deleted for some reason language: nonEqlLanguages, - index: indexOrUndefined, - dataViewId: dataViewIdOrUndefined, - query: queryOrUndefined, - filters: filtersOrUndefined, + index: t.union([IndexPatternArray, t.undefined]), + dataViewId: t.union([DataViewId, t.undefined]), + query: t.union([RuleQuery, t.undefined]), + filters: t.union([RuleFilterArray, t.undefined]), savedId: saved_id, responseActions: ResponseActionRuleParamsOrUndefined, }); @@ -198,12 +199,12 @@ export type UnifiedQueryRuleParams = t.TypeOf<typeof unifiedQueryRuleParams>; const thresholdSpecificRuleParams = t.type({ type: t.literal('threshold'), language: nonEqlLanguages, - index: indexOrUndefined, - query, - filters: filtersOrUndefined, + index: t.union([IndexPatternArray, t.undefined]), + query: RuleQuery, + filters: t.union([RuleFilterArray, t.undefined]), savedId: savedIdOrUndefined, - threshold: thresholdNormalized, - dataViewId: dataViewIdOrUndefined, + threshold: ThresholdNormalized, + dataViewId: t.union([DataViewId, t.undefined]), }); export const thresholdRuleParams = t.intersection([baseRuleParams, thresholdSpecificRuleParams]); export type ThresholdSpecificRuleParams = t.TypeOf<typeof thresholdSpecificRuleParams>; @@ -223,13 +224,13 @@ export type MachineLearningRuleParams = t.TypeOf<typeof machineLearningRuleParam const newTermsSpecificRuleParams = t.type({ type: t.literal('new_terms'), - query, - newTermsFields, - historyWindowStart, - index: indexOrUndefined, - filters: filtersOrUndefined, + query: RuleQuery, + newTermsFields: NewTermsFields, + historyWindowStart: HistoryWindowStart, + index: t.union([IndexPatternArray, t.undefined]), + filters: t.union([RuleFilterArray, t.undefined]), language: nonEqlLanguages, - dataViewId: dataViewIdOrUndefined, + dataViewId: t.union([DataViewId, t.undefined]), }); export const newTermsRuleParams = t.intersection([baseRuleParams, newTermsSpecificRuleParams]); export type NewTermsSpecificRuleParams = t.TypeOf<typeof newTermsSpecificRuleParams>; @@ -274,30 +275,30 @@ export const allRuleTypes = t.union([ ]); export const internalRuleCreate = t.type({ - name, - tags, + name: RuleName, + tags: RuleTagArray, alertTypeId: allRuleTypes, consumer: t.literal(SERVER_APP_ID), schedule: t.type({ interval: t.string, }), - enabled, - actions: actionsCamel, + enabled: IsRuleEnabled, + actions: RuleActionArrayCamel, params: ruleParams, - throttle: throttleOrNull, + throttle: t.union([RuleActionThrottle, t.null]), notifyWhen, }); export type InternalRuleCreate = t.TypeOf<typeof internalRuleCreate>; export const internalRuleUpdate = t.type({ - name, - tags, + name: RuleName, + tags: RuleTagArray, schedule: t.type({ interval: t.string, }), - actions: actionsCamel, + actions: RuleActionArrayCamel, params: ruleParams, - throttle: throttleOrNull, + throttle: t.union([RuleActionThrottle, t.null]), notifyWhen, }); export type InternalRuleUpdate = t.TypeOf<typeof internalRuleUpdate>; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts index 505560ee6a25c..8ec684b2065fe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RuleParams } from '../../schemas/rule_schemas'; +import type { RuleParams } from '../../rule_schema'; export const createRuleMock = (params: Partial<RuleParams>) => ({ actions: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts index bfe25804de29a..a76e3d3d3af7e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/rule_type.ts @@ -20,10 +20,10 @@ import type { ConfigType } from '../../../../config'; import type { AlertAttributes } from '../../signals/types'; import { createRuleMock } from './rule'; import { listMock } from '@kbn/lists-plugin/server/mocks'; -import type { QueryRuleParams, RuleParams } from '../../schemas/rule_schemas'; +import type { QueryRuleParams, RuleParams } from '../../rule_schema'; // this is only used in tests import { createDefaultAlertExecutorOptions } from '@kbn/rule-registry-plugin/server/utils/rule_executor.test_helpers'; -import { getCompleteRuleMock } from '../../schemas/rule_schemas.mock'; +import { getCompleteRuleMock } from '../../rule_schema/mocks'; export const createRuleTypeMocks = ( ruleType: string = 'query', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/threshold.ts index 21107ba361645..ab6d64e69b58d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/threshold.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/threshold.ts @@ -26,7 +26,7 @@ import { flattenWithPrefix } from '@kbn/securitysolution-rules'; import type { TypeOfFieldMap } from '@kbn/rule-registry-plugin/common/field_map'; import { SERVER_APP_ID } from '../../../../../common/constants'; -import { ANCHOR_DATE } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; +import { ANCHOR_DATE } from '../../../../../common/detection_engine/rule_schema/mocks'; import { getListArrayMock } from '../../../../../common/detection_engine/schemas/types/lists.mock'; import type { RulesFieldMap } from '../../../../../common/field_maps'; import { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index 05813ed1ee29c..bd630b1a71107 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -25,14 +25,18 @@ import { import { DEFAULT_MAX_SIGNALS, DEFAULT_SEARCH_AFTER_PAGE_SIZE } from '../../../../common/constants'; import type { CreateSecurityRuleTypeWrapper } from './types'; import { getListClient } from './utils/get_list_client'; -import type { NotificationRuleTypeParams } from '../notifications/schedule_notification_actions'; -import { scheduleNotificationActions } from '../notifications/schedule_notification_actions'; -import { getNotificationResultsLink } from '../notifications/utils'; +// eslint-disable-next-line no-restricted-imports +import type { NotificationRuleTypeParams } from '../rule_actions_legacy'; +// eslint-disable-next-line no-restricted-imports +import { + scheduleNotificationActions, + scheduleThrottledNotificationActions, + getNotificationResultsLink, +} from '../rule_actions_legacy'; import { createResultObject } from './utils'; import { bulkCreateFactory, wrapHitsFactory, wrapSequencesFactory } from './factories'; import { RuleExecutionStatus } from '../../../../common/detection_engine/rule_monitoring'; import { truncateList } from '../rule_monitoring'; -import { scheduleThrottledNotificationActions } from '../notifications/schedule_throttle_notification_actions'; import aadFieldConversion from '../routes/index/signal_aad_mapping.json'; import { extractReferences, injectReferences } from '../signals/saved_object_references'; import { withSecuritySpan } from '../../../utils/with_security_span'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts index 0d1a8462ac738..6e31a9753d381 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/eql/create_eql_alert_type.ts @@ -9,8 +9,8 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { EQL_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import { SERVER_APP_ID } from '../../../../../common/constants'; -import type { EqlRuleParams } from '../../schemas/rule_schemas'; -import { eqlRuleParams } from '../../schemas/rule_schemas'; +import type { EqlRuleParams } from '../../rule_schema'; +import { eqlRuleParams } from '../../rule_schema'; import { eqlExecutor } from '../../signals/executors/eql'; import type { CreateRuleOptions, SecurityAlertType } from '../types'; import { validateIndexPatterns } from '../utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts index b1f9ed1361da1..5e6cccd11e037 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts @@ -41,7 +41,7 @@ import { ALERT_BUILDING_BLOCK_TYPE, ALERT_RULE_INDICES, } from '../../../../../../common/field_maps/field_names'; -import { getCompleteRuleMock, getQueryRuleParams } from '../../../schemas/rule_schemas.mock'; +import { getCompleteRuleMock, getQueryRuleParams } from '../../../rule_schema/mocks'; type SignalDoc = SignalSourceHit & { _source: Required<SignalSourceHit>['_source'] & { [TIMESTAMP]: string }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts index 38115347a8241..c716af17361ec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.ts @@ -74,11 +74,8 @@ import { ALERT_RULE_EXCEPTIONS_LIST, ALERT_RULE_IMMUTABLE, } from '../../../../../../common/field_maps/field_names'; -import type { CompleteRule, RuleParams } from '../../../schemas/rule_schemas'; -import { - commonParamsCamelToSnake, - typeSpecificCamelToSnake, -} from '../../../schemas/rule_converters'; +import type { CompleteRule, RuleParams } from '../../../rule_schema'; +import { commonParamsCamelToSnake, typeSpecificCamelToSnake } from '../../../rule_management'; import { transformAlertToRuleAction } from '../../../../../../common/detection_engine/transform_actions'; import type { AncestorLatest, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts index d90bc09fb7794..e047c86e75473 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.test.ts @@ -16,8 +16,8 @@ import { objectPairIntersection, } from './build_alert_group_from_sequence'; import { SERVER_APP_ID } from '../../../../../../common/constants'; -import { getCompleteRuleMock, getQueryRuleParams } from '../../../schemas/rule_schemas.mock'; -import type { QueryRuleParams } from '../../../schemas/rule_schemas'; +import { getCompleteRuleMock, getQueryRuleParams } from '../../../rule_schema/mocks'; +import type { QueryRuleParams } from '../../../rule_schema'; import { ALERT_ANCESTORS, ALERT_DEPTH, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts index ccd089e767308..700986198468c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert_group_from_sequence.ts @@ -16,7 +16,7 @@ import { buildBulkBody } from './build_bulk_body'; import type { EqlSequence } from '../../../../../../common/detection_engine/types'; import { generateBuildingBlockIds } from './generate_building_block_ids'; import type { BuildReasonMessage } from '../../../signals/reason_formatters'; -import type { CompleteRule, RuleParams } from '../../../schemas/rule_schemas'; +import type { CompleteRule, RuleParams } from '../../../rule_schema'; import { ALERT_BUILDING_BLOCK_TYPE, ALERT_GROUP_ID, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts index 5ea566ce33af0..4d63f5c848484 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_bulk_body.ts @@ -15,7 +15,7 @@ import { getMergeStrategy } from '../../../signals/source_fields_merging/strateg import type { BaseSignalHit, SignalSource, SignalSourceHit } from '../../../signals/types'; import { additionalAlertFields, buildAlert } from './build_alert'; import { filterSource } from './filter_source'; -import type { CompleteRule, RuleParams } from '../../../schemas/rule_schemas'; +import type { CompleteRule, RuleParams } from '../../../rule_schema'; import { buildRuleNameFromMapping } from '../../../signals/mappings/build_rule_name_from_mapping'; import { buildSeverityFromMapping } from '../../../signals/mappings/build_severity_from_mapping'; import { buildRiskScoreFromMapping } from '../../../signals/mappings/build_risk_score_from_mapping'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/wrap_new_terms_alerts.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/wrap_new_terms_alerts.test.ts index dfdd2ce715ee4..6742f21759648 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/wrap_new_terms_alerts.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/wrap_new_terms_alerts.test.ts @@ -7,7 +7,7 @@ import { ALERT_UUID } from '@kbn/rule-data-utils'; import { ALERT_NEW_TERMS } from '../../../../../../common/field_maps/field_names'; -import { getCompleteRuleMock, getNewTermsRuleParams } from '../../../schemas/rule_schemas.mock'; +import { getCompleteRuleMock, getNewTermsRuleParams } from '../../../rule_schema/mocks'; import { sampleDocNoSortIdWithTimestamp } from '../../../signals/__mocks__/es_results'; import { wrapNewTermsAlerts } from './wrap_new_terms_alerts'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/wrap_new_terms_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/wrap_new_terms_alerts.ts index f3e1434b75f2b..38ae5b1be75ae 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/wrap_new_terms_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/wrap_new_terms_alerts.ts @@ -15,7 +15,7 @@ import type { } from '../../../../../../common/detection_engine/schemas/alerts'; import { ALERT_NEW_TERMS } from '../../../../../../common/field_maps/field_names'; import type { ConfigType } from '../../../../../config'; -import type { CompleteRule, RuleParams } from '../../../schemas/rule_schemas'; +import type { CompleteRule, RuleParams } from '../../../rule_schema'; import { buildReasonMessageForNewTermsAlert } from '../../../signals/reason_formatters'; import type { SignalSource } from '../../../signals/types'; import { buildBulkBody } from './build_bulk_body'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts index bf4c3a8aa46e3..75d55a5756680 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts @@ -10,7 +10,7 @@ import { ALERT_UUID } from '@kbn/rule-data-utils'; import type { ConfigType } from '../../../../config'; import type { SignalSource, SimpleHit } from '../../signals/types'; -import type { CompleteRule, RuleParams } from '../../schemas/rule_schemas'; +import type { CompleteRule, RuleParams } from '../../rule_schema'; import { generateId } from '../../signals/utils'; import { buildBulkBody } from './utils/build_bulk_body'; import type { BuildReasonMessage } from '../../signals/reason_formatters'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts index ccf959daa9e7a..b700c31cf7c72 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_sequences_factory.ts @@ -10,7 +10,7 @@ import type { Logger } from '@kbn/core/server'; import type { WrapSequences } from '../../signals/types'; import { buildAlertGroupFromSequence } from './utils/build_alert_group_from_sequence'; import type { ConfigType } from '../../../../config'; -import type { CompleteRule, RuleParams } from '../../schemas/rule_schemas'; +import type { CompleteRule, RuleParams } from '../../rule_schema'; import type { BaseFieldsLatest, WrappedFieldsLatest, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts index fe0bc6fe77a7c..796900829d6ea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/create_indicator_match_alert_type.ts @@ -9,8 +9,8 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { INDICATOR_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import { SERVER_APP_ID } from '../../../../../common/constants'; -import type { ThreatRuleParams } from '../../schemas/rule_schemas'; -import { threatRuleParams } from '../../schemas/rule_schemas'; +import type { ThreatRuleParams } from '../../rule_schema'; +import { threatRuleParams } from '../../rule_schema'; import { threatMatchExecutor } from '../../signals/executors/threat_match'; import type { CreateRuleOptions, SecurityAlertType } from '../types'; import { validateIndexPatterns } from '../utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts index f70d6d82087c9..c652de348484f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/ml/create_ml_alert_type.ts @@ -9,8 +9,8 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { ML_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import { SERVER_APP_ID } from '../../../../../common/constants'; -import type { MachineLearningRuleParams } from '../../schemas/rule_schemas'; -import { machineLearningRuleParams } from '../../schemas/rule_schemas'; +import type { MachineLearningRuleParams } from '../../rule_schema'; +import { machineLearningRuleParams } from '../../rule_schema'; import { mlExecutor } from '../../signals/executors/ml'; import type { CreateRuleOptions, SecurityAlertType } from '../types'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts index 2e22d3a5798bf..630553bc4d78c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts @@ -10,8 +10,8 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { NEW_TERMS_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import { SERVER_APP_ID } from '../../../../../common/constants'; -import type { NewTermsRuleParams } from '../../schemas/rule_schemas'; -import { newTermsRuleParams } from '../../schemas/rule_schemas'; +import type { NewTermsRuleParams } from '../../rule_schema'; +import { newTermsRuleParams } from '../../rule_schema'; import type { CreateRuleOptions, SecurityAlertType } from '../types'; import { singleSearchAfter } from '../../signals/single_search_after'; import { getFilter } from '../../signals/get_filter'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts index dfd0ffc6c4353..c2ecb5a88df8e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.test.ts @@ -15,7 +15,7 @@ import { createMockConfig } from '../../routes/__mocks__'; import { createMockTelemetryEventsSender } from '../../../telemetry/__mocks__'; import { ruleExecutionLogMock } from '../../rule_monitoring/mocks'; import { sampleDocNoSortId } from '../../signals/__mocks__/es_results'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { getQueryRuleParams } from '../../rule_schema/mocks'; import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; jest.mock('../../signals/utils', () => ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts index 4ce8a1dacabf7..4f246e5ada204 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/create_query_alert_type.ts @@ -9,8 +9,8 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { QUERY_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import { SERVER_APP_ID } from '../../../../../common/constants'; -import type { UnifiedQueryRuleParams } from '../../schemas/rule_schemas'; -import { unifiedQueryRuleParams } from '../../schemas/rule_schemas'; +import type { UnifiedQueryRuleParams } from '../../rule_schema'; +import { unifiedQueryRuleParams } from '../../rule_schema'; import { queryExecutor } from '../../signals/executors/query'; import type { CreateQueryRuleOptions, SecurityAlertType } from '../types'; import { validateIndexPatterns } from '../utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts index 6565e6e14cfea..6e761bb6a51a0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/saved_query/create_saved_query_alert_type.ts @@ -9,8 +9,8 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { SAVED_QUERY_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import { SERVER_APP_ID } from '../../../../../common/constants'; -import type { CompleteRule, UnifiedQueryRuleParams } from '../../schemas/rule_schemas'; -import { unifiedQueryRuleParams } from '../../schemas/rule_schemas'; +import type { CompleteRule, UnifiedQueryRuleParams } from '../../rule_schema'; +import { unifiedQueryRuleParams } from '../../rule_schema'; import { queryExecutor } from '../../signals/executors/query'; import type { CreateQueryRuleOptions, SecurityAlertType } from '../types'; import { validateIndexPatterns } from '../utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts index 1541b08c5938c..b465abed15977 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/create_threshold_alert_type.ts @@ -9,8 +9,8 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { THRESHOLD_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import { SERVER_APP_ID } from '../../../../../common/constants'; -import type { ThresholdRuleParams } from '../../schemas/rule_schemas'; -import { thresholdRuleParams } from '../../schemas/rule_schemas'; +import type { ThresholdRuleParams } from '../../rule_schema'; +import { thresholdRuleParams } from '../../rule_schema'; import { thresholdExecutor } from '../../signals/executors/threshold'; import type { ThresholdAlertState } from '../../signals/types'; import type { CreateRuleOptions, SecurityAlertType } from '../types'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts index e1af3077ec3e3..03634c19b0ba0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/types.ts @@ -29,7 +29,7 @@ import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; import type { Filter } from '@kbn/es-query'; import type { ConfigType } from '../../../config'; import type { SetupPlugins } from '../../../plugin'; -import type { CompleteRule, RuleParams } from '../schemas/rule_schemas'; +import type { CompleteRule, RuleParams } from '../rule_schema'; import type { BulkCreate, SearchAfterAndBulkCreateReturnType, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts deleted file mode 100644 index 0075b091d58a4..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.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 { getPrepackagedRules } from './get_prepackaged_rules'; -import { isEmpty } from 'lodash/fp'; -import type { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; - -describe('get_existing_prepackaged_rules', () => { - test('should not throw any errors with the existing checked in pre-packaged rules', () => { - expect(() => getPrepackagedRules()).not.toThrow(); - }); - - test('no rule should have the same rule_id as another rule_id', () => { - const prePackagedRules = getPrepackagedRules(); - let existingRuleIds: AddPrepackagedRulesSchema[] = []; - prePackagedRules.forEach((rule) => { - const foundDuplicate = existingRuleIds.reduce((accum, existingRule) => { - if (existingRule.rule_id === rule.rule_id) { - return `Found duplicate rule_id of ${rule.rule_id} between these two rule names of "${rule.name}" and "${existingRule.name}"`; - } else { - return accum; - } - }, ''); - if (!isEmpty(foundDuplicate)) { - expect(foundDuplicate).toEqual(''); - } else { - existingRuleIds = [...existingRuleIds, rule]; - } - }); - }); - - test('should throw an exception if a pre-packaged rule is not valid', () => { - // @ts-expect-error intentionally invalid argument - expect(() => getPrepackagedRules([{ not_valid_made_up_key: true }])).toThrow(); - }); - - test('should throw an exception with a message having rule_id and name in it', () => { - expect(() => - // @ts-expect-error intentionally invalid argument - getPrepackagedRules([{ name: 'rule name', rule_id: 'id-123' }]) - ).toThrow(); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts deleted file mode 100644 index 97878f9eb2e0b..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ /dev/null @@ -1,1427 +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. - */ - -// Auto generated file from either: -// - scripts/regen_prepackage_rules_index.sh -// - detection-rules repo using CLI command build-release -// Do not hand edit. Run script/command to regenerate package information instead - -import rule1 from './credential_access_access_to_browser_credentials_procargs.json'; -import rule2 from './defense_evasion_tcc_bypass_mounted_apfs_access.json'; -import rule3 from './persistence_enable_root_account.json'; -import rule4 from './defense_evasion_unload_endpointsecurity_kext.json'; -import rule5 from './persistence_account_creation_hide_at_logon.json'; -import rule6 from './persistence_creation_hidden_login_item_osascript.json'; -import rule7 from './persistence_evasion_hidden_launch_agent_deamon_creation.json'; -import rule8 from './privilege_escalation_local_user_added_to_admin.json'; -import rule9 from './credential_access_keychain_pwd_retrieval_security_cmd.json'; -import rule10 from './credential_access_systemkey_dumping.json'; -import rule11 from './execution_defense_evasion_electron_app_childproc_node_js.json'; -import rule12 from './execution_revershell_via_shell_cmd.json'; -import rule13 from './persistence_defense_evasion_hidden_launch_agent_deamon_logonitem_process.json'; -import rule14 from './privilege_escalation_persistence_phantom_dll.json'; -import rule15 from './defense_evasion_privilege_escalation_privacy_pref_sshd_fulldiskaccess.json'; -import rule16 from './lateral_movement_credential_access_kerberos_bifrostconsole.json'; -import rule17 from './lateral_movement_vpn_connection_attempt.json'; -import rule18 from './apm_403_response_to_a_post.json'; -import rule19 from './apm_405_response_method_not_allowed.json'; -import rule20 from './apm_sqlmap_user_agent.json'; -import rule21 from './command_and_control_linux_iodine_activity.json'; -import rule22 from './command_and_control_nat_traversal_port_activity.json'; -import rule23 from './command_and_control_port_26_activity.json'; -import rule24 from './command_and_control_rdp_remote_desktop_protocol_from_the_internet.json'; -import rule25 from './command_and_control_telnet_port_activity.json'; -import rule26 from './command_and_control_vnc_virtual_network_computing_from_the_internet.json'; -import rule27 from './command_and_control_vnc_virtual_network_computing_to_the_internet.json'; -import rule28 from './credential_access_endgame_cred_dumping_detected.json'; -import rule29 from './credential_access_endgame_cred_dumping_prevented.json'; -import rule30 from './defense_evasion_adding_the_hidden_file_attribute_with_via_attribexe.json'; -import rule31 from './defense_evasion_clearing_windows_event_logs.json'; -import rule32 from './defense_evasion_delete_volume_usn_journal_with_fsutil.json'; -import rule33 from './defense_evasion_disable_windows_firewall_rules_with_netsh.json'; -import rule34 from './defense_evasion_misc_lolbin_connecting_to_the_internet.json'; -import rule35 from './defense_evasion_msbuild_making_network_connections.json'; -import rule36 from './defense_evasion_suspicious_certutil_commands.json'; -import rule37 from './defense_evasion_unusual_network_connection_via_rundll32.json'; -import rule38 from './defense_evasion_unusual_process_network_connection.json'; -import rule39 from './defense_evasion_via_filter_manager.json'; -import rule40 from './discovery_linux_hping_activity.json'; -import rule41 from './discovery_linux_nping_activity.json'; -import rule42 from './discovery_whoami_command_activity.json'; -import rule43 from './endgame_adversary_behavior_detected.json'; -import rule44 from './endgame_malware_detected.json'; -import rule45 from './endgame_malware_prevented.json'; -import rule46 from './endgame_ransomware_detected.json'; -import rule47 from './endgame_ransomware_prevented.json'; -import rule48 from './execution_command_prompt_connecting_to_the_internet.json'; -import rule49 from './execution_command_shell_started_by_svchost.json'; -import rule50 from './execution_endgame_exploit_detected.json'; -import rule51 from './execution_endgame_exploit_prevented.json'; -import rule52 from './execution_html_help_executable_program_connecting_to_the_internet.json'; -import rule53 from './execution_linux_netcat_network_connection.json'; -import rule54 from './execution_psexec_lateral_movement_command.json'; -import rule55 from './execution_register_server_program_connecting_to_the_internet.json'; -import rule56 from './execution_via_compiled_html_file.json'; -import rule57 from './impact_deleting_backup_catalogs_with_wbadmin.json'; -import rule58 from './impact_volume_shadow_copy_deletion_or_resized_via_vssadmin.json'; -import rule59 from './impact_volume_shadow_copy_deletion_via_wmic.json'; -import rule60 from './initial_access_rpc_remote_procedure_call_from_the_internet.json'; -import rule61 from './initial_access_rpc_remote_procedure_call_to_the_internet.json'; -import rule62 from './initial_access_script_executing_powershell.json'; -import rule63 from './initial_access_smb_windows_file_sharing_activity_to_the_internet.json'; -import rule64 from './initial_access_suspicious_ms_office_child_process.json'; -import rule65 from './initial_access_suspicious_ms_outlook_child_process.json'; -import rule66 from './lateral_movement_direct_outbound_smb_connection.json'; -import rule67 from './lateral_movement_service_control_spawned_script_int.json'; -import rule68 from './persistence_adobe_hijack_persistence.json'; -import rule69 from './persistence_local_scheduled_task_creation.json'; -import rule70 from './persistence_priv_escalation_via_accessibility_features.json'; -import rule71 from './persistence_shell_activity_by_web_server.json'; -import rule72 from './persistence_system_shells_via_services.json'; -import rule73 from './persistence_user_account_creation.json'; -import rule74 from './persistence_via_application_shimming.json'; -import rule75 from './privilege_escalation_endgame_cred_manipulation_detected.json'; -import rule76 from './privilege_escalation_endgame_cred_manipulation_prevented.json'; -import rule77 from './privilege_escalation_endgame_permission_theft_detected.json'; -import rule78 from './privilege_escalation_endgame_permission_theft_prevented.json'; -import rule79 from './privilege_escalation_endgame_process_injection_detected.json'; -import rule80 from './privilege_escalation_endgame_process_injection_prevented.json'; -import rule81 from './privilege_escalation_unusual_parentchild_relationship.json'; -import rule82 from './impact_modification_of_boot_config.json'; -import rule83 from './privilege_escalation_uac_bypass_event_viewer.json'; -import rule84 from './defense_evasion_msxsl_network.json'; -import rule85 from './discovery_command_system_account.json'; -import rule86 from './command_and_control_certutil_network_connection.json'; -import rule87 from './defense_evasion_cve_2020_0601.json'; -import rule88 from './command_and_control_ml_packetbeat_dns_tunneling.json'; -import rule89 from './command_and_control_ml_packetbeat_rare_dns_question.json'; -import rule90 from './command_and_control_ml_packetbeat_rare_urls.json'; -import rule91 from './command_and_control_ml_packetbeat_rare_user_agent.json'; -import rule92 from './credential_access_credential_dumping_msbuild.json'; -import rule93 from './credential_access_ml_suspicious_login_activity.json'; -import rule94 from './defense_evasion_execution_msbuild_started_by_office_app.json'; -import rule95 from './defense_evasion_execution_msbuild_started_by_script.json'; -import rule96 from './defense_evasion_execution_msbuild_started_by_system_process.json'; -import rule97 from './defense_evasion_execution_msbuild_started_renamed.json'; -import rule98 from './defense_evasion_execution_msbuild_started_unusal_process.json'; -import rule99 from './defense_evasion_injection_msbuild.json'; -import rule100 from './execution_ml_windows_anomalous_script.json'; -import rule101 from './initial_access_ml_linux_anomalous_user_name.json'; -import rule102 from './initial_access_ml_windows_anomalous_user_name.json'; -import rule103 from './initial_access_ml_windows_rare_user_type10_remote_login.json'; -import rule104 from './ml_linux_anomalous_network_activity.json'; -import rule105 from './ml_linux_anomalous_network_port_activity.json'; -import rule106 from './ml_packetbeat_rare_server_domain.json'; -import rule107 from './ml_windows_anomalous_network_activity.json'; -import rule108 from './persistence_ml_linux_anomalous_process_all_hosts.json'; -import rule109 from './persistence_ml_rare_process_by_host_linux.json'; -import rule110 from './persistence_ml_rare_process_by_host_windows.json'; -import rule111 from './persistence_ml_windows_anomalous_path_activity.json'; -import rule112 from './persistence_ml_windows_anomalous_process_all_hosts.json'; -import rule113 from './persistence_ml_windows_anomalous_process_creation.json'; -import rule114 from './persistence_ml_windows_anomalous_service.json'; -import rule115 from './privilege_escalation_ml_windows_rare_user_runas_event.json'; -import rule116 from './execution_suspicious_pdf_reader.json'; -import rule117 from './privilege_escalation_sudoers_file_mod.json'; -import rule118 from './defense_evasion_iis_httplogging_disabled.json'; -import rule119 from './execution_python_tty_shell.json'; -import rule120 from './execution_perl_tty_shell.json'; -import rule121 from './defense_evasion_base16_or_base32_encoding_or_decoding_activity.json'; -import rule122 from './defense_evasion_file_mod_writable_dir.json'; -import rule123 from './defense_evasion_disable_selinux_attempt.json'; -import rule124 from './discovery_kernel_module_enumeration.json'; -import rule125 from './lateral_movement_telnet_network_activity_external.json'; -import rule126 from './lateral_movement_telnet_network_activity_internal.json'; -import rule127 from './privilege_escalation_setuid_setgid_bit_set_via_chmod.json'; -import rule128 from './defense_evasion_kernel_module_removal.json'; -import rule129 from './defense_evasion_attempt_to_disable_syslog_service.json'; -import rule130 from './defense_evasion_file_deletion_via_shred.json'; -import rule131 from './discovery_virtual_machine_fingerprinting.json'; -import rule132 from './defense_evasion_hidden_file_dir_tmp.json'; -import rule133 from './defense_evasion_deletion_of_bash_command_line_history.json'; -import rule134 from './impact_cloudwatch_log_group_deletion.json'; -import rule135 from './impact_cloudwatch_log_stream_deletion.json'; -import rule136 from './impact_rds_instance_cluster_stoppage.json'; -import rule137 from './persistence_attempt_to_deactivate_mfa_for_okta_user_account.json'; -import rule138 from './persistence_rds_cluster_creation.json'; -import rule139 from './credential_access_attempted_bypass_of_okta_mfa.json'; -import rule140 from './defense_evasion_okta_attempt_to_deactivate_okta_policy.json'; -import rule141 from './defense_evasion_okta_attempt_to_deactivate_okta_policy_rule.json'; -import rule142 from './defense_evasion_okta_attempt_to_modify_okta_network_zone.json'; -import rule143 from './defense_evasion_okta_attempt_to_modify_okta_policy.json'; -import rule144 from './defense_evasion_okta_attempt_to_modify_okta_policy_rule.json'; -import rule145 from './defense_evasion_waf_acl_deletion.json'; -import rule146 from './impact_attempt_to_revoke_okta_api_token.json'; -import rule147 from './impact_iam_group_deletion.json'; -import rule148 from './impact_possible_okta_dos_attack.json'; -import rule149 from './impact_rds_instance_cluster_deletion.json'; -import rule150 from './initial_access_suspicious_activity_reported_by_okta_user.json'; -import rule151 from './okta_threat_detected_by_okta_threatinsight.json'; -import rule152 from './persistence_administrator_privileges_assigned_to_okta_group.json'; -import rule153 from './persistence_attempt_to_create_okta_api_token.json'; -import rule154 from './persistence_attempt_to_reset_mfa_factors_for_okta_user_account.json'; -import rule155 from './defense_evasion_cloudtrail_logging_deleted.json'; -import rule156 from './defense_evasion_ec2_network_acl_deletion.json'; -import rule157 from './impact_iam_deactivate_mfa_device.json'; -import rule158 from './defense_evasion_s3_bucket_configuration_deletion.json'; -import rule159 from './defense_evasion_guardduty_detector_deletion.json'; -import rule160 from './defense_evasion_okta_attempt_to_delete_okta_policy.json'; -import rule161 from './credential_access_iam_user_addition_to_group.json'; -import rule162 from './persistence_ec2_network_acl_creation.json'; -import rule163 from './impact_ec2_disable_ebs_encryption.json'; -import rule164 from './persistence_iam_group_creation.json'; -import rule165 from './defense_evasion_waf_rule_or_rule_group_deletion.json'; -import rule166 from './collection_cloudtrail_logging_created.json'; -import rule167 from './defense_evasion_cloudtrail_logging_suspended.json'; -import rule168 from './impact_cloudtrail_logging_updated.json'; -import rule169 from './initial_access_console_login_root.json'; -import rule170 from './defense_evasion_cloudwatch_alarm_deletion.json'; -import rule171 from './defense_evasion_ec2_flow_log_deletion.json'; -import rule172 from './defense_evasion_configuration_recorder_stopped.json'; -import rule173 from './exfiltration_ec2_snapshot_change_activity.json'; -import rule174 from './defense_evasion_config_service_rule_deletion.json'; -import rule175 from './persistence_okta_attempt_to_modify_or_delete_application_sign_on_policy.json'; -import rule176 from './command_and_control_download_rar_powershell_from_internet.json'; -import rule177 from './initial_access_password_recovery.json'; -import rule178 from './command_and_control_cobalt_strike_beacon.json'; -import rule179 from './command_and_control_fin7_c2_behavior.json'; -import rule180 from './command_and_control_halfbaked_beacon.json'; -import rule181 from './credential_access_secretsmanager_getsecretvalue.json'; -import rule182 from './initial_access_via_system_manager.json'; -import rule183 from './privilege_escalation_root_login_without_mfa.json'; -import rule184 from './privilege_escalation_updateassumerolepolicy.json'; -import rule185 from './impact_hosts_file_modified.json'; -import rule186 from './elastic_endpoint_security.json'; -import rule187 from './external_alerts.json'; -import rule188 from './ml_cloudtrail_error_message_spike.json'; -import rule189 from './ml_cloudtrail_rare_error_code.json'; -import rule190 from './ml_cloudtrail_rare_method_by_city.json'; -import rule191 from './ml_cloudtrail_rare_method_by_country.json'; -import rule192 from './ml_cloudtrail_rare_method_by_user.json'; -import rule193 from './credential_access_aws_iam_assume_role_brute_force.json'; -import rule194 from './credential_access_okta_brute_force_or_password_spraying.json'; -import rule195 from './initial_access_unusual_dns_service_children.json'; -import rule196 from './initial_access_unusual_dns_service_file_writes.json'; -import rule197 from './lateral_movement_dns_server_overflow.json'; -import rule198 from './credential_access_root_console_failure_brute_force.json'; -import rule199 from './initial_access_unsecure_elasticsearch_node.json'; -import rule200 from './impact_virtual_network_device_modified.json'; -import rule201 from './credential_access_domain_backup_dpapi_private_keys.json'; -import rule202 from './persistence_gpo_schtask_service_creation.json'; -import rule203 from './credential_access_credentials_keychains.json'; -import rule204 from './credential_access_kerberosdump_kcc.json'; -import rule205 from './defense_evasion_attempt_del_quarantine_attrib.json'; -import rule206 from './execution_suspicious_psexesvc.json'; -import rule207 from './execution_via_xp_cmdshell_mssql_stored_procedure.json'; -import rule208 from './privilege_escalation_printspooler_service_suspicious_file.json'; -import rule209 from './privilege_escalation_printspooler_suspicious_spl_file.json'; -import rule210 from './defense_evasion_azure_diagnostic_settings_deletion.json'; -import rule211 from './execution_command_virtual_machine.json'; -import rule212 from './execution_via_hidden_shell_conhost.json'; -import rule213 from './impact_resource_group_deletion.json'; -import rule214 from './persistence_via_telemetrycontroller_scheduledtask_hijack.json'; -import rule215 from './persistence_via_update_orchestrator_service_hijack.json'; -import rule216 from './collection_update_event_hub_auth_rule.json'; -import rule217 from './credential_access_iis_apppoolsa_pwd_appcmd.json'; -import rule218 from './credential_access_iis_connectionstrings_dumping.json'; -import rule219 from './defense_evasion_event_hub_deletion.json'; -import rule220 from './defense_evasion_firewall_policy_deletion.json'; -import rule221 from './defense_evasion_sdelete_like_filename_rename.json'; -import rule222 from './lateral_movement_remote_ssh_login_enabled.json'; -import rule223 from './persistence_azure_automation_account_created.json'; -import rule224 from './persistence_azure_automation_runbook_created_or_modified.json'; -import rule225 from './persistence_azure_automation_webhook_created.json'; -import rule226 from './privilege_escalation_uac_bypass_diskcleanup_hijack.json'; -import rule227 from './credential_access_attempts_to_brute_force_okta_user_account.json'; -import rule228 from './credential_access_storage_account_key_regenerated.json'; -import rule229 from './defense_evasion_suspicious_okta_user_password_reset_or_unlock_attempts.json'; -import rule230 from './defense_evasion_system_critical_proc_abnormal_file_activity.json'; -import rule231 from './defense_evasion_unusual_system_vp_child_program.json'; -import rule232 from './discovery_blob_container_access_mod.json'; -import rule233 from './persistence_mfa_disabled_for_azure_user.json'; -import rule234 from './persistence_user_added_as_owner_for_azure_application.json'; -import rule235 from './persistence_user_added_as_owner_for_azure_service_principal.json'; -import rule236 from './defense_evasion_dotnet_compiler_parent_process.json'; -import rule237 from './defense_evasion_suspicious_managedcode_host_process.json'; -import rule238 from './execution_command_shell_started_by_unusual_process.json'; -import rule239 from './defense_evasion_masquerading_as_elastic_endpoint_process.json'; -import rule240 from './defense_evasion_masquerading_suspicious_werfault_childproc.json'; -import rule241 from './defense_evasion_masquerading_werfault.json'; -import rule242 from './credential_access_bruteforce_admin_account.json'; -import rule243 from './credential_access_bruteforce_multiple_logon_failure_followed_by_success.json'; -import rule244 from './credential_access_bruteforce_multiple_logon_failure_same_srcip.json'; -import rule245 from './credential_access_key_vault_modified.json'; -import rule246 from './credential_access_mimikatz_memssp_default_logs.json'; -import rule247 from './defense_evasion_network_watcher_deletion.json'; -import rule248 from './initial_access_external_guest_user_invite.json'; -import rule249 from './defense_evasion_azure_automation_runbook_deleted.json'; -import rule250 from './defense_evasion_masquerading_renamed_autoit.json'; -import rule251 from './initial_access_consent_grant_attack_via_azure_registered_application.json'; -import rule252 from './persistence_azure_conditional_access_policy_modified.json'; -import rule253 from './persistence_azure_privileged_identity_management_role_modified.json'; -import rule254 from './command_and_control_teamviewer_remote_file_copy.json'; -import rule255 from './defense_evasion_installutil_beacon.json'; -import rule256 from './defense_evasion_mshta_beacon.json'; -import rule257 from './defense_evasion_network_connection_from_windows_binary.json'; -import rule258 from './defense_evasion_rundll32_no_arguments.json'; -import rule259 from './defense_evasion_suspicious_scrobj_load.json'; -import rule260 from './defense_evasion_suspicious_wmi_script.json'; -import rule261 from './execution_ms_office_written_file.json'; -import rule262 from './execution_pdf_written_file.json'; -import rule263 from './lateral_movement_cmd_service.json'; -import rule264 from './persistence_app_compat_shim.json'; -import rule265 from './command_and_control_remote_file_copy_desktopimgdownldr.json'; -import rule266 from './command_and_control_remote_file_copy_mpcmdrun.json'; -import rule267 from './defense_evasion_execution_suspicious_explorer_winword.json'; -import rule268 from './defense_evasion_suspicious_zoom_child_process.json'; -import rule269 from './discovery_ml_linux_system_information_discovery.json'; -import rule270 from './discovery_ml_linux_system_network_configuration_discovery.json'; -import rule271 from './discovery_ml_linux_system_network_connection_discovery.json'; -import rule272 from './discovery_ml_linux_system_process_discovery.json'; -import rule273 from './discovery_ml_linux_system_user_discovery.json'; -import rule274 from './privilege_escalation_ml_linux_anomalous_sudo_activity.json'; -import rule275 from './resource_development_ml_linux_anomalous_compiler_activity.json'; -import rule276 from './discovery_post_exploitation_external_ip_lookup.json'; -import rule277 from './initial_access_zoom_meeting_with_no_passcode.json'; -import rule278 from './defense_evasion_gcp_logging_sink_deletion.json'; -import rule279 from './defense_evasion_gcp_pub_sub_topic_deletion.json'; -import rule280 from './defense_evasion_gcp_firewall_rule_created.json'; -import rule281 from './defense_evasion_gcp_firewall_rule_deleted.json'; -import rule282 from './defense_evasion_gcp_firewall_rule_modified.json'; -import rule283 from './defense_evasion_gcp_logging_bucket_deletion.json'; -import rule284 from './defense_evasion_gcp_storage_bucket_permissions_modified.json'; -import rule285 from './impact_gcp_storage_bucket_deleted.json'; -import rule286 from './initial_access_gcp_iam_custom_role_creation.json'; -import rule287 from './persistence_gcp_iam_service_account_key_deletion.json'; -import rule288 from './persistence_gcp_key_created_for_service_account.json'; -import rule289 from './credential_access_ml_linux_anomalous_metadata_process.json'; -import rule290 from './credential_access_ml_linux_anomalous_metadata_user.json'; -import rule291 from './credential_access_ml_windows_anomalous_metadata_process.json'; -import rule292 from './credential_access_ml_windows_anomalous_metadata_user.json'; -import rule293 from './defense_evasion_gcp_storage_bucket_configuration_modified.json'; -import rule294 from './defense_evasion_gcp_virtual_private_cloud_network_deleted.json'; -import rule295 from './defense_evasion_gcp_virtual_private_cloud_route_created.json'; -import rule296 from './defense_evasion_gcp_virtual_private_cloud_route_deleted.json'; -import rule297 from './exfiltration_gcp_logging_sink_modification.json'; -import rule298 from './impact_gcp_iam_role_deletion.json'; -import rule299 from './impact_gcp_service_account_deleted.json'; -import rule300 from './impact_gcp_service_account_disabled.json'; -import rule301 from './persistence_gcp_service_account_created.json'; -import rule302 from './collection_gcp_pub_sub_subscription_creation.json'; -import rule303 from './collection_gcp_pub_sub_topic_creation.json'; -import rule304 from './defense_evasion_gcp_pub_sub_subscription_deletion.json'; -import rule305 from './persistence_azure_pim_user_added_global_admin.json'; -import rule306 from './command_and_control_cobalt_strike_default_teamserver_cert.json'; -import rule307 from './defense_evasion_enable_inbound_rdp_with_netsh.json'; -import rule308 from './defense_evasion_execution_lolbas_wuauclt.json'; -import rule309 from './privilege_escalation_unusual_svchost_childproc_childless.json'; -import rule310 from './command_and_control_rdp_tunnel_plink.json'; -import rule311 from './privilege_escalation_uac_bypass_winfw_mmc_hijack.json'; -import rule312 from './discovery_privileged_localgroup_membership.json'; -import rule313 from './persistence_ms_office_addins_file.json'; -import rule314 from './discovery_adfind_command_activity.json'; -import rule315 from './discovery_security_software_wmic.json'; -import rule316 from './execution_command_shell_via_rundll32.json'; -import rule317 from './execution_suspicious_cmd_wmi.json'; -import rule318 from './lateral_movement_via_startup_folder_rdp_smb.json'; -import rule319 from './privilege_escalation_uac_bypass_com_interface_icmluautil.json'; -import rule320 from './privilege_escalation_uac_bypass_mock_windir.json'; -import rule321 from './defense_evasion_potential_processherpaderping.json'; -import rule322 from './privilege_escalation_uac_bypass_dll_sideloading.json'; -import rule323 from './execution_shared_modules_local_sxs_dll.json'; -import rule324 from './privilege_escalation_uac_bypass_com_clipup.json'; -import rule325 from './initial_access_via_explorer_suspicious_child_parent_args.json'; -import rule326 from './defense_evasion_from_unusual_directory.json'; -import rule327 from './execution_from_unusual_path_cmdline.json'; -import rule328 from './credential_access_kerberoasting_unusual_process.json'; -import rule329 from './discovery_peripheral_device.json'; -import rule330 from './lateral_movement_mount_hidden_or_webdav_share_net.json'; -import rule331 from './defense_evasion_deleting_websvr_access_logs.json'; -import rule332 from './defense_evasion_log_files_deleted.json'; -import rule333 from './defense_evasion_timestomp_touch.json'; -import rule334 from './lateral_movement_dcom_hta.json'; -import rule335 from './lateral_movement_execution_via_file_shares_sequence.json'; -import rule336 from './privilege_escalation_uac_bypass_com_ieinstal.json'; -import rule337 from './command_and_control_common_webservices.json'; -import rule338 from './command_and_control_encrypted_channel_freesslcert.json'; -import rule339 from './defense_evasion_process_termination_followed_by_deletion.json'; -import rule340 from './lateral_movement_remote_file_copy_hidden_share.json'; -import rule341 from './defense_evasion_attempt_to_deactivate_okta_network_zone.json'; -import rule342 from './defense_evasion_attempt_to_delete_okta_network_zone.json'; -import rule343 from './defense_evasion_okta_attempt_to_delete_okta_policy_rule.json'; -import rule344 from './impact_okta_attempt_to_deactivate_okta_application.json'; -import rule345 from './impact_okta_attempt_to_delete_okta_application.json'; -import rule346 from './impact_okta_attempt_to_modify_okta_application.json'; -import rule347 from './lateral_movement_dcom_mmc20.json'; -import rule348 from './lateral_movement_dcom_shellwindow_shellbrowserwindow.json'; -import rule349 from './persistence_administrator_role_assigned_to_okta_user.json'; -import rule350 from './lateral_movement_executable_tool_transfer_smb.json'; -import rule351 from './command_and_control_dns_tunneling_nslookup.json'; -import rule352 from './lateral_movement_execution_from_tsclient_mup.json'; -import rule353 from './lateral_movement_rdp_sharprdp_target.json'; -import rule354 from './defense_evasion_clearing_windows_security_logs.json'; -import rule355 from './persistence_google_workspace_api_access_granted_via_domain_wide_delegation_of_authority.json'; -import rule356 from './defense_evasion_suspicious_short_program_name.json'; -import rule357 from './lateral_movement_incoming_wmi.json'; -import rule358 from './persistence_via_hidden_run_key_valuename.json'; -import rule359 from './credential_access_potential_macos_ssh_bruteforce.json'; -import rule360 from './credential_access_promt_for_pwd_via_osascript.json'; -import rule361 from './lateral_movement_remote_services.json'; -import rule362 from './defense_evasion_domain_added_to_google_workspace_trusted_domains.json'; -import rule363 from './execution_suspicious_image_load_wmi_ms_office.json'; -import rule364 from './execution_suspicious_powershell_imgload.json'; -import rule365 from './impact_google_workspace_admin_role_deletion.json'; -import rule366 from './impact_google_workspace_mfa_enforcement_disabled.json'; -import rule367 from './persistence_application_added_to_google_workspace_domain.json'; -import rule368 from './persistence_evasion_registry_ifeo_injection.json'; -import rule369 from './persistence_google_workspace_admin_role_assigned_to_user.json'; -import rule370 from './persistence_google_workspace_custom_admin_role_created.json'; -import rule371 from './persistence_google_workspace_policy_modified.json'; -import rule372 from './persistence_google_workspace_role_modified.json'; -import rule373 from './persistence_mfa_disabled_for_google_workspace_organization.json'; -import rule374 from './persistence_suspicious_image_load_scheduled_task_ms_office.json'; -import rule375 from './defense_evasion_masquerading_trusted_directory.json'; -import rule376 from './exfiltration_microsoft_365_exchange_transport_rule_creation.json'; -import rule377 from './initial_access_microsoft_365_exchange_safelinks_disabled.json'; -import rule378 from './persistence_appcertdlls_registry.json'; -import rule379 from './persistence_appinitdlls_registry.json'; -import rule380 from './persistence_microsoft_365_exchange_dkim_signing_config_disabled.json'; -import rule381 from './persistence_registry_uncommon.json'; -import rule382 from './persistence_run_key_and_startup_broad.json'; -import rule383 from './persistence_services_registry.json'; -import rule384 from './persistence_startup_folder_file_written_by_suspicious_process.json'; -import rule385 from './persistence_startup_folder_scripts.json'; -import rule386 from './persistence_suspicious_com_hijack_registry.json'; -import rule387 from './persistence_via_lsa_security_support_provider_registry.json'; -import rule388 from './defense_evasion_microsoft_365_exchange_malware_filter_policy_deletion.json'; -import rule389 from './defense_evasion_microsoft_365_exchange_malware_filter_rule_mod.json'; -import rule390 from './defense_evasion_microsoft_365_exchange_safe_attach_rule_disabled.json'; -import rule391 from './exfiltration_microsoft_365_exchange_transport_rule_mod.json'; -import rule392 from './initial_access_microsoft_365_exchange_anti_phish_policy_deletion.json'; -import rule393 from './initial_access_microsoft_365_exchange_anti_phish_rule_mod.json'; -import rule394 from './lateral_movement_suspicious_rdp_client_imageload.json'; -import rule395 from './persistence_runtime_run_key_startup_susp_procs.json'; -import rule396 from './persistence_suspicious_scheduled_task_runtime.json'; -import rule397 from './defense_evasion_microsoft_365_exchange_dlp_policy_removed.json'; -import rule398 from './lateral_movement_scheduled_task_target.json'; -import rule399 from './persistence_microsoft_365_exchange_management_role_assignment.json'; -import rule400 from './persistence_microsoft_365_teams_guest_access_enabled.json'; -import rule401 from './credential_access_dump_registry_hives.json'; -import rule402 from './defense_evasion_scheduledjobs_at_protocol_enabled.json'; -import rule403 from './persistence_ms_outlook_vba_template.json'; -import rule404 from './persistence_suspicious_service_created_registry.json'; -import rule405 from './privilege_escalation_named_pipe_impersonation.json'; -import rule406 from './credential_access_cmdline_dump_tool.json'; -import rule407 from './credential_access_copy_ntds_sam_volshadowcp_cmdline.json'; -import rule408 from './credential_access_lsass_memdump_file_created.json'; -import rule409 from './lateral_movement_incoming_winrm_shell_execution.json'; -import rule410 from './lateral_movement_powershell_remoting_target.json'; -import rule411 from './command_and_control_port_forwarding_added_registry.json'; -import rule412 from './defense_evasion_hide_encoded_executable_registry.json'; -import rule413 from './lateral_movement_rdp_enabled_registry.json'; -import rule414 from './privilege_escalation_printspooler_registry_copyfiles.json'; -import rule415 from './privilege_escalation_rogue_windir_environment_var.json'; -import rule416 from './initial_access_scripts_process_started_via_wmi.json'; -import rule417 from './command_and_control_iexplore_via_com.json'; -import rule418 from './command_and_control_remote_file_copy_scripts.json'; -import rule419 from './persistence_local_scheduled_task_scripting.json'; -import rule420 from './persistence_startup_folder_file_written_by_unsigned_process.json'; -import rule421 from './command_and_control_remote_file_copy_powershell.json'; -import rule422 from './credential_access_microsoft_365_brute_force_user_account_attempt.json'; -import rule423 from './persistence_microsoft_365_teams_custom_app_interaction_allowed.json'; -import rule424 from './persistence_microsoft_365_teams_external_access_enabled.json'; -import rule425 from './credential_access_microsoft_365_potential_password_spraying_attack.json'; -import rule426 from './impact_stop_process_service_threshold.json'; -import rule427 from './collection_winrar_encryption.json'; -import rule428 from './defense_evasion_unusual_dir_ads.json'; -import rule429 from './discovery_admin_recon.json'; -import rule430 from './discovery_net_view.json'; -import rule431 from './discovery_remote_system_discovery_commands_windows.json'; -import rule432 from './persistence_via_windows_management_instrumentation_event_subscription.json'; -import rule433 from './credential_access_mimikatz_powershell_module.json'; -import rule434 from './execution_scripting_osascript_exec_followed_by_netcon.json'; -import rule435 from './execution_shell_execution_via_apple_scripting.json'; -import rule436 from './persistence_creation_change_launch_agents_file.json'; -import rule437 from './persistence_creation_modif_launch_deamon_sequence.json'; -import rule438 from './persistence_folder_action_scripts_runtime.json'; -import rule439 from './persistence_login_logout_hooks_defaults.json'; -import rule440 from './privilege_escalation_explicit_creds_via_scripting.json'; -import rule441 from './command_and_control_sunburst_c2_activity_detected.json'; -import rule442 from './defense_evasion_azure_application_credential_modification.json'; -import rule443 from './defense_evasion_azure_service_principal_addition.json'; -import rule444 from './defense_evasion_solarwinds_backdoor_service_disabled_via_registry.json'; -import rule445 from './execution_apt_solarwinds_backdoor_child_cmd_powershell.json'; -import rule446 from './execution_apt_solarwinds_backdoor_unusual_child_processes.json'; -import rule447 from './initial_access_azure_active_directory_powershell_signin.json'; -import rule448 from './collection_email_powershell_exchange_mailbox.json'; -import rule449 from './execution_scheduled_task_powershell_source.json'; -import rule450 from './persistence_powershell_exch_mailbox_activesync_add_device.json'; -import rule451 from './persistence_docker_shortcuts_plist_modification.json'; -import rule452 from './persistence_evasion_hidden_local_account_creation.json'; -import rule453 from './persistence_finder_sync_plugin_pluginkit.json'; -import rule454 from './discovery_security_software_grep.json'; -import rule455 from './credential_access_cookies_chromium_browsers_debugging.json'; -import rule456 from './credential_access_ssh_backdoor_log.json'; -import rule457 from './persistence_credential_access_modify_auth_module_or_config.json'; -import rule458 from './persistence_credential_access_modify_ssh_binaries.json'; -import rule459 from './credential_access_collection_sensitive_files.json'; -import rule460 from './persistence_ssh_authorized_keys_modification.json'; -import rule461 from './defense_evasion_defender_disabled_via_registry.json'; -import rule462 from './defense_evasion_privacy_controls_tcc_database_modification.json'; -import rule463 from './execution_initial_access_suspicious_browser_childproc.json'; -import rule464 from './execution_script_via_automator_workflows.json'; -import rule465 from './persistence_modification_sublime_app_plugin_or_script.json'; -import rule466 from './privilege_escalation_applescript_with_admin_privs.json'; -import rule467 from './credential_access_dumping_keychain_security.json'; -import rule468 from './initial_access_azure_active_directory_high_risk_signin.json'; -import rule469 from './initial_access_suspicious_mac_ms_office_child_process.json'; -import rule470 from './credential_access_mitm_localhost_webproxy.json'; -import rule471 from './persistence_kde_autostart_modification.json'; -import rule472 from './persistence_user_account_added_to_privileged_group_ad.json'; -import rule473 from './defense_evasion_attempt_to_disable_gatekeeper.json'; -import rule474 from './defense_evasion_sandboxed_office_app_suspicious_zip_file.json'; -import rule475 from './persistence_emond_rules_file_creation.json'; -import rule476 from './persistence_emond_rules_process_execution.json'; -import rule477 from './discovery_users_domain_built_in_commands.json'; -import rule478 from './execution_pentest_eggshell_remote_admin_tool.json'; -import rule479 from './defense_evasion_install_root_certificate.json'; -import rule480 from './persistence_credential_access_authorization_plugin_creation.json'; -import rule481 from './persistence_directory_services_plugins_modification.json'; -import rule482 from './defense_evasion_modify_environment_launchctl.json'; -import rule483 from './defense_evasion_safari_config_change.json'; -import rule484 from './defense_evasion_apple_softupdates_modification.json'; -import rule485 from './credential_access_mod_wdigest_security_provider.json'; -import rule486 from './credential_access_saved_creds_vaultcmd.json'; -import rule487 from './defense_evasion_file_creation_mult_extension.json'; -import rule488 from './execution_enumeration_via_wmiprvse.json'; -import rule489 from './execution_suspicious_jar_child_process.json'; -import rule490 from './persistence_shell_profile_modification.json'; -import rule491 from './persistence_suspicious_calendar_modification.json'; -import rule492 from './persistence_time_provider_mod.json'; -import rule493 from './privilege_escalation_exploit_adobe_acrobat_updater.json'; -import rule494 from './defense_evasion_sip_provider_mod.json'; -import rule495 from './execution_com_object_xwizard.json'; -import rule496 from './privilege_escalation_disable_uac_registry.json'; -import rule497 from './defense_evasion_unusual_ads_file_creation.json'; -import rule498 from './persistence_loginwindow_plist_modification.json'; -import rule499 from './persistence_periodic_tasks_file_mdofiy.json'; -import rule500 from './persistence_via_atom_init_file_modification.json'; -import rule501 from './privilege_escalation_lsa_auth_package.json'; -import rule502 from './privilege_escalation_port_monitor_print_pocessor_abuse.json'; -import rule503 from './credential_access_dumping_hashes_bi_cmds.json'; -import rule504 from './lateral_movement_mounting_smb_share.json'; -import rule505 from './privilege_escalation_echo_nopasswd_sudoers.json'; -import rule506 from './privilege_escalation_ld_preload_shared_object_modif.json'; -import rule507 from './privilege_escalation_root_crontab_filemod.json'; -import rule508 from './defense_evasion_create_mod_root_certificate.json'; -import rule509 from './privilege_escalation_sudo_buffer_overflow.json'; -import rule510 from './execution_installer_package_spawned_network_event.json'; -import rule511 from './initial_access_suspicious_ms_exchange_files.json'; -import rule512 from './initial_access_suspicious_ms_exchange_process.json'; -import rule513 from './initial_access_suspicious_ms_exchange_worker_child_process.json'; -import rule514 from './persistence_evasion_registry_startup_shell_folder_modified.json'; -import rule515 from './persistence_local_scheduled_job_creation.json'; -import rule516 from './persistence_via_wmi_stdregprov_run_services.json'; -import rule517 from './credential_access_persistence_network_logon_provider_modification.json'; -import rule518 from './lateral_movement_defense_evasion_lanman_nullsessionpipe_modification.json'; -import rule519 from './collection_microsoft_365_new_inbox_rule.json'; -import rule520 from './ml_high_count_network_denies.json'; -import rule521 from './ml_high_count_network_events.json'; -import rule522 from './ml_rare_destination_country.json'; -import rule523 from './ml_spike_in_traffic_to_a_country.json'; -import rule524 from './command_and_control_tunneling_via_earthworm.json'; -import rule525 from './lateral_movement_evasion_rdp_shadowing.json'; -import rule526 from './threat_intel_fleet_integrations.json'; -import rule527 from './exfiltration_ec2_vm_export_failure.json'; -import rule528 from './exfiltration_ec2_full_network_packet_capture_detected.json'; -import rule529 from './impact_azure_service_principal_credentials_added.json'; -import rule530 from './persistence_ec2_security_group_configuration_change_detection.json'; -import rule531 from './defense_evasion_disabling_windows_logs.json'; -import rule532 from './persistence_route_53_domain_transfer_lock_disabled.json'; -import rule533 from './persistence_route_53_domain_transferred_to_another_account.json'; -import rule534 from './initial_access_okta_user_attempted_unauthorized_access.json'; -import rule535 from './credential_access_user_excessive_sso_logon_errors.json'; -import rule536 from './persistence_exchange_suspicious_mailbox_right_delegation.json'; -import rule537 from './privilege_escalation_new_or_modified_federation_domain.json'; -import rule538 from './privilege_escalation_sts_assumerole_usage.json'; -import rule539 from './privilege_escalation_sts_getsessiontoken_abuse.json'; -import rule540 from './defense_evasion_suspicious_execution_from_mounted_device.json'; -import rule541 from './defense_evasion_unusual_network_connection_via_dllhost.json'; -import rule542 from './defense_evasion_amsienable_key_mod.json'; -import rule543 from './impact_rds_group_deletion.json'; -import rule544 from './persistence_rds_group_creation.json'; -import rule545 from './persistence_route_table_created.json'; -import rule546 from './persistence_route_table_modified_or_deleted.json'; -import rule547 from './exfiltration_rds_snapshot_export.json'; -import rule548 from './persistence_rds_instance_creation.json'; -import rule549 from './privilege_escalation_gcp_kubernetes_rolebindings_created_or_patched.json'; -import rule550 from './credential_access_ml_auth_spike_in_failed_logon_events.json'; -import rule551 from './credential_access_ml_auth_spike_in_logon_events.json'; -import rule552 from './credential_access_ml_auth_spike_in_logon_events_from_a_source_ip.json'; -import rule553 from './initial_access_ml_auth_rare_hour_for_a_user_to_logon.json'; -import rule554 from './initial_access_ml_auth_rare_source_ip_for_a_user.json'; -import rule555 from './initial_access_ml_auth_rare_user_logon.json'; -import rule556 from './privilege_escalation_cyberarkpas_error_audit_event_promotion.json'; -import rule557 from './privilege_escalation_cyberarkpas_recommended_events_to_monitor_promotion.json'; -import rule558 from './defense_evasion_kubernetes_events_deleted.json'; -import rule559 from './impact_kubernetes_pod_deleted.json'; -import rule560 from './exfiltration_rds_snapshot_restored.json'; -import rule561 from './privilege_escalation_printspooler_suspicious_file_deletion.json'; -import rule562 from './privilege_escalation_unusual_printspooler_childprocess.json'; -import rule563 from './defense_evasion_disabling_windows_defender_powershell.json'; -import rule564 from './defense_evasion_enable_network_discovery_with_netsh.json'; -import rule565 from './defense_evasion_execution_windefend_unusual_path.json'; -import rule566 from './defense_evasion_agent_spoofing_mismatched_id.json'; -import rule567 from './defense_evasion_agent_spoofing_multiple_hosts.json'; -import rule568 from './defense_evasion_parent_process_pid_spoofing.json'; -import rule569 from './impact_microsoft_365_potential_ransomware_activity.json'; -import rule570 from './impact_microsoft_365_unusual_volume_of_file_deletion.json'; -import rule571 from './initial_access_microsoft_365_user_restricted_from_sending_email.json'; -import rule572 from './defense_evasion_elasticache_security_group_creation.json'; -import rule573 from './defense_evasion_elasticache_security_group_modified_or_deleted.json'; -import rule574 from './impact_volume_shadow_copy_deletion_via_powershell.json'; -import rule575 from './persistence_route_53_hosted_zone_associated_with_a_vpc.json'; -import rule576 from './defense_evasion_defender_exclusion_via_powershell.json'; -import rule577 from './defense_evasion_dns_over_https_enabled.json'; -import rule578 from './defense_evasion_frontdoor_firewall_policy_deletion.json'; -import rule579 from './credential_access_azure_full_network_packet_capture_detected.json'; -import rule580 from './persistence_webshell_detection.json'; -import rule581 from './defense_evasion_suppression_rule_created.json'; -import rule582 from './impact_efs_filesystem_or_mount_deleted.json'; -import rule583 from './defense_evasion_execution_control_panel_suspicious_args.json'; -import rule584 from './defense_evasion_azure_blob_permissions_modified.json'; -import rule585 from './privilege_escalation_aws_suspicious_saml_activity.json'; -import rule586 from './credential_access_potential_lsa_memdump_via_mirrordump.json'; -import rule587 from './discovery_virtual_machine_fingerprinting_grep.json'; -import rule588 from './impact_backup_file_deletion.json'; -import rule589 from './credential_access_posh_minidump.json'; -import rule590 from './persistence_screensaver_engine_unexpected_child_process.json'; -import rule591 from './persistence_screensaver_plist_file_modification.json'; -import rule592 from './credential_access_suspicious_lsass_access_memdump.json'; -import rule593 from './defense_evasion_suspicious_process_access_direct_syscall.json'; -import rule594 from './discovery_posh_suspicious_api_functions.json'; -import rule595 from './privilege_escalation_via_rogue_named_pipe.json'; -import rule596 from './credential_access_suspicious_lsass_access_via_snapshot.json'; -import rule597 from './defense_evasion_posh_process_injection.json'; -import rule598 from './collection_posh_keylogger.json'; -import rule599 from './defense_evasion_posh_assembly_load.json'; -import rule600 from './defense_evasion_powershell_windows_firewall_disabled.json'; -import rule601 from './execution_posh_portable_executable.json'; -import rule602 from './execution_posh_psreflect.json'; -import rule603 from './credential_access_suspicious_comsvcs_imageload.json'; -import rule604 from './impact_aws_eventbridge_rule_disabled_or_deleted.json'; -import rule605 from './defense_evasion_microsoft_defender_tampering.json'; -import rule606 from './initial_access_azure_active_directory_high_risk_signin_atrisk_or_confirmed.json'; -import rule607 from './persistence_remote_password_reset.json'; -import rule608 from './privilege_escalation_azure_kubernetes_rolebinding_created.json'; -import rule609 from './collection_posh_audio_capture.json'; -import rule610 from './collection_posh_screen_grabber.json'; -import rule611 from './defense_evasion_posh_compressed.json'; -import rule612 from './defense_evasion_suspicious_process_creation_calltrace.json'; -import rule613 from './privilege_escalation_group_policy_iniscript.json'; -import rule614 from './privilege_escalation_group_policy_privileged_groups.json'; -import rule615 from './privilege_escalation_group_policy_scheduled_task.json'; -import rule616 from './defense_evasion_clearing_windows_console_history.json'; -import rule617 from './threat_intel_filebeat8x.json'; -import rule618 from './privilege_escalation_installertakeover.json'; -import rule619 from './credential_access_via_snapshot_lsass_clone_creation.json'; -import rule620 from './persistence_via_bits_job_notify_command.json'; -import rule621 from './execution_suspicious_java_netcon_childproc.json'; -import rule622 from './privilege_escalation_samaccountname_spoofing_attack.json'; -import rule623 from './credential_access_symbolic_link_to_shadow_copy_created.json'; -import rule624 from './credential_access_mfa_push_brute_force.json'; -import rule625 from './persistence_azure_global_administrator_role_assigned.json'; -import rule626 from './persistence_microsoft_365_global_administrator_role_assign.json'; -import rule627 from './lateral_movement_malware_uploaded_onedrive.json'; -import rule628 from './lateral_movement_malware_uploaded_sharepoint.json'; -import rule629 from './defense_evasion_ms_office_suspicious_regmod.json'; -import rule630 from './initial_access_o365_user_reported_phish_malware.json'; -import rule631 from './defense_evasion_microsoft_365_mailboxauditbypassassociation.json'; -import rule632 from './credential_access_disable_kerberos_preauth.json'; -import rule633 from './credential_access_posh_request_ticket.json'; -import rule634 from './credential_access_shadow_credentials.json'; -import rule635 from './privilege_escalation_pkexec_envar_hijack.json'; -import rule636 from './credential_access_seenabledelegationprivilege_assigned_to_user.json'; -import rule637 from './persistence_msds_alloweddelegateto_krbtgt.json'; -import rule638 from './defense_evasion_disable_posh_scriptblocklogging.json'; -import rule639 from './persistence_ad_adminsdholder.json'; -import rule640 from './privilege_escalation_windows_service_via_unusual_client.json'; -import rule641 from './credential_access_dcsync_replication_rights.json'; -import rule642 from './credential_access_lsass_memdump_handle_access.json'; -import rule643 from './credential_access_moving_registry_hive_via_smb.json'; -import rule644 from './credential_access_suspicious_winreg_access_via_sebackup_priv.json'; -import rule645 from './credential_access_spn_attribute_modified.json'; -import rule646 from './persistence_dontexpirepasswd_account.json'; -import rule647 from './persistence_sdprop_exclusion_dsheuristics.json'; -import rule648 from './credential_access_remote_sam_secretsdump.json'; -import rule649 from './defense_evasion_workfolders_control_execution.json'; -import rule650 from './credential_access_user_impersonation_access.json'; -import rule651 from './persistence_redshift_instance_creation.json'; -import rule652 from './persistence_crontab_creation.json'; -import rule653 from './privilege_escalation_krbrelayup_service_creation.json'; -import rule654 from './credential_access_relay_ntlm_auth_via_http_spoolss.json'; -import rule655 from './execution_shell_evasion_linux_binary.json'; -import rule656 from './execution_process_started_in_shared_memory_directory.json'; -import rule657 from './execution_abnormal_process_id_file_created.json'; -import rule658 from './execution_process_started_from_process_id_file.json'; -import rule659 from './privilege_escalation_suspicious_dnshostname_update.json'; -import rule660 from './command_and_control_connection_attempt_by_non_ssh_root_session.json'; -import rule661 from './execution_user_exec_to_pod.json'; -import rule662 from './defense_evasion_elastic_agent_service_terminated.json'; -import rule663 from './defense_evasion_proxy_execution_via_msdt.json'; -import rule664 from './discovery_enumerating_domain_trusts_via_nltest.json'; -import rule665 from './credential_access_lsass_handle_via_malseclogon.json'; -import rule666 from './discovery_suspicious_self_subject_review.json'; -import rule667 from './initial_access_evasion_suspicious_htm_file_creation.json'; -import rule668 from './persistence_exposed_service_created_with_type_nodeport.json'; -import rule669 from './privilege_escalation_pod_created_with_hostipc.json'; -import rule670 from './privilege_escalation_pod_created_with_hostnetwork.json'; -import rule671 from './privilege_escalation_pod_created_with_hostpid.json'; -import rule672 from './privilege_escalation_privileged_pod_created.json'; -import rule673 from './execution_tc_bpf_filter.json'; -import rule674 from './persistence_insmod_kernel_module_load.json'; -import rule675 from './privilege_escalation_pod_created_with_sensitive_hospath_volume.json'; -import rule676 from './persistence_dynamic_linker_backup.json'; -import rule677 from './defense_evasion_hidden_shared_object.json'; -import rule678 from './defense_evasion_chattr_immutable_file.json'; -import rule679 from './persistence_chkconfig_service_add.json'; -import rule680 from './persistence_etc_file_creation.json'; -import rule681 from './impact_process_kill_threshold.json'; -import rule682 from './discovery_posh_invoke_sharefinder.json'; -import rule683 from './privilege_escalation_posh_token_impersonation.json'; -import rule684 from './collection_google_drive_ownership_transferred_via_google_workspace.json'; -import rule685 from './persistence_google_workspace_user_group_access_modified_to_allow_external_access.json'; -import rule686 from './defense_evasion_application_removed_from_blocklist_in_google_workspace.json'; -import rule687 from './defense_evasion_google_workspace_restrictions_for_google_marketplace_changed_to_allow_any_app.json'; -import rule688 from './persistence_google_workspace_2sv_policy_disabled.json'; -import rule689 from './credential_access_generic_localdumps.json'; -import rule690 from './defense_evasion_persistence_temp_scheduled_task.json'; -import rule691 from './lateral_movement_remote_task_creation_winlog.json'; -import rule692 from './persistence_scheduled_task_creation_winlog.json'; -import rule693 from './persistence_scheduled_task_updated.json'; -import rule694 from './credential_access_saved_creds_vault_winlog.json'; -import rule695 from './privilege_escalation_create_process_as_different_user.json'; -import rule696 from './privilege_escalation_unshare_namesapce_manipulation.json'; -import rule697 from './privilege_escalation_shadow_file_read.json'; -import rule698 from './defense_evasion_google_workspace_bitlocker_setting_disabled.json'; -import rule699 from './persistence_google_workspace_user_organizational_unit_changed.json'; -import rule700 from './collection_google_workspace_custom_gmail_route_created_or_modified.json'; -import rule701 from './discovery_denied_service_account_request.json'; -import rule702 from './initial_access_anonymous_request_authorized.json'; -import rule703 from './privilege_escalation_suspicious_assignment_of_controller_service_account.json'; -import rule704 from './credential_access_bruteforce_passowrd_guessing.json'; -import rule705 from './credential_access_potential_linux_ssh_bruteforce.json'; -import rule706 from './credential_access_potential_linux_ssh_bruteforce_root.json'; - -export const rawRules = [ - rule1, - rule2, - rule3, - rule4, - rule5, - rule6, - rule7, - rule8, - rule9, - rule10, - rule11, - rule12, - rule13, - rule14, - rule15, - rule16, - rule17, - rule18, - rule19, - rule20, - rule21, - rule22, - rule23, - rule24, - rule25, - rule26, - rule27, - rule28, - rule29, - rule30, - rule31, - rule32, - rule33, - rule34, - rule35, - rule36, - rule37, - rule38, - rule39, - rule40, - rule41, - rule42, - rule43, - rule44, - rule45, - rule46, - rule47, - rule48, - rule49, - rule50, - rule51, - rule52, - rule53, - rule54, - rule55, - rule56, - rule57, - rule58, - rule59, - rule60, - rule61, - rule62, - rule63, - rule64, - rule65, - rule66, - rule67, - rule68, - rule69, - rule70, - rule71, - rule72, - rule73, - rule74, - rule75, - rule76, - rule77, - rule78, - rule79, - rule80, - rule81, - rule82, - rule83, - rule84, - rule85, - rule86, - rule87, - rule88, - rule89, - rule90, - rule91, - rule92, - rule93, - rule94, - rule95, - rule96, - rule97, - rule98, - rule99, - rule100, - rule101, - rule102, - rule103, - rule104, - rule105, - rule106, - rule107, - rule108, - rule109, - rule110, - rule111, - rule112, - rule113, - rule114, - rule115, - rule116, - rule117, - rule118, - rule119, - rule120, - rule121, - rule122, - rule123, - rule124, - rule125, - rule126, - rule127, - rule128, - rule129, - rule130, - rule131, - rule132, - rule133, - rule134, - rule135, - rule136, - rule137, - rule138, - rule139, - rule140, - rule141, - rule142, - rule143, - rule144, - rule145, - rule146, - rule147, - rule148, - rule149, - rule150, - rule151, - rule152, - rule153, - rule154, - rule155, - rule156, - rule157, - rule158, - rule159, - rule160, - rule161, - rule162, - rule163, - rule164, - rule165, - rule166, - rule167, - rule168, - rule169, - rule170, - rule171, - rule172, - rule173, - rule174, - rule175, - rule176, - rule177, - rule178, - rule179, - rule180, - rule181, - rule182, - rule183, - rule184, - rule185, - rule186, - rule187, - rule188, - rule189, - rule190, - rule191, - rule192, - rule193, - rule194, - rule195, - rule196, - rule197, - rule198, - rule199, - rule200, - rule201, - rule202, - rule203, - rule204, - rule205, - rule206, - rule207, - rule208, - rule209, - rule210, - rule211, - rule212, - rule213, - rule214, - rule215, - rule216, - rule217, - rule218, - rule219, - rule220, - rule221, - rule222, - rule223, - rule224, - rule225, - rule226, - rule227, - rule228, - rule229, - rule230, - rule231, - rule232, - rule233, - rule234, - rule235, - rule236, - rule237, - rule238, - rule239, - rule240, - rule241, - rule242, - rule243, - rule244, - rule245, - rule246, - rule247, - rule248, - rule249, - rule250, - rule251, - rule252, - rule253, - rule254, - rule255, - rule256, - rule257, - rule258, - rule259, - rule260, - rule261, - rule262, - rule263, - rule264, - rule265, - rule266, - rule267, - rule268, - rule269, - rule270, - rule271, - rule272, - rule273, - rule274, - rule275, - rule276, - rule277, - rule278, - rule279, - rule280, - rule281, - rule282, - rule283, - rule284, - rule285, - rule286, - rule287, - rule288, - rule289, - rule290, - rule291, - rule292, - rule293, - rule294, - rule295, - rule296, - rule297, - rule298, - rule299, - rule300, - rule301, - rule302, - rule303, - rule304, - rule305, - rule306, - rule307, - rule308, - rule309, - rule310, - rule311, - rule312, - rule313, - rule314, - rule315, - rule316, - rule317, - rule318, - rule319, - rule320, - rule321, - rule322, - rule323, - rule324, - rule325, - rule326, - rule327, - rule328, - rule329, - rule330, - rule331, - rule332, - rule333, - rule334, - rule335, - rule336, - rule337, - rule338, - rule339, - rule340, - rule341, - rule342, - rule343, - rule344, - rule345, - rule346, - rule347, - rule348, - rule349, - rule350, - rule351, - rule352, - rule353, - rule354, - rule355, - rule356, - rule357, - rule358, - rule359, - rule360, - rule361, - rule362, - rule363, - rule364, - rule365, - rule366, - rule367, - rule368, - rule369, - rule370, - rule371, - rule372, - rule373, - rule374, - rule375, - rule376, - rule377, - rule378, - rule379, - rule380, - rule381, - rule382, - rule383, - rule384, - rule385, - rule386, - rule387, - rule388, - rule389, - rule390, - rule391, - rule392, - rule393, - rule394, - rule395, - rule396, - rule397, - rule398, - rule399, - rule400, - rule401, - rule402, - rule403, - rule404, - rule405, - rule406, - rule407, - rule408, - rule409, - rule410, - rule411, - rule412, - rule413, - rule414, - rule415, - rule416, - rule417, - rule418, - rule419, - rule420, - rule421, - rule422, - rule423, - rule424, - rule425, - rule426, - rule427, - rule428, - rule429, - rule430, - rule431, - rule432, - rule433, - rule434, - rule435, - rule436, - rule437, - rule438, - rule439, - rule440, - rule441, - rule442, - rule443, - rule444, - rule445, - rule446, - rule447, - rule448, - rule449, - rule450, - rule451, - rule452, - rule453, - rule454, - rule455, - rule456, - rule457, - rule458, - rule459, - rule460, - rule461, - rule462, - rule463, - rule464, - rule465, - rule466, - rule467, - rule468, - rule469, - rule470, - rule471, - rule472, - rule473, - rule474, - rule475, - rule476, - rule477, - rule478, - rule479, - rule480, - rule481, - rule482, - rule483, - rule484, - rule485, - rule486, - rule487, - rule488, - rule489, - rule490, - rule491, - rule492, - rule493, - rule494, - rule495, - rule496, - rule497, - rule498, - rule499, - rule500, - rule501, - rule502, - rule503, - rule504, - rule505, - rule506, - rule507, - rule508, - rule509, - rule510, - rule511, - rule512, - rule513, - rule514, - rule515, - rule516, - rule517, - rule518, - rule519, - rule520, - rule521, - rule522, - rule523, - rule524, - rule525, - rule526, - rule527, - rule528, - rule529, - rule530, - rule531, - rule532, - rule533, - rule534, - rule535, - rule536, - rule537, - rule538, - rule539, - rule540, - rule541, - rule542, - rule543, - rule544, - rule545, - rule546, - rule547, - rule548, - rule549, - rule550, - rule551, - rule552, - rule553, - rule554, - rule555, - rule556, - rule557, - rule558, - rule559, - rule560, - rule561, - rule562, - rule563, - rule564, - rule565, - rule566, - rule567, - rule568, - rule569, - rule570, - rule571, - rule572, - rule573, - rule574, - rule575, - rule576, - rule577, - rule578, - rule579, - rule580, - rule581, - rule582, - rule583, - rule584, - rule585, - rule586, - rule587, - rule588, - rule589, - rule590, - rule591, - rule592, - rule593, - rule594, - rule595, - rule596, - rule597, - rule598, - rule599, - rule600, - rule601, - rule602, - rule603, - rule604, - rule605, - rule606, - rule607, - rule608, - rule609, - rule610, - rule611, - rule612, - rule613, - rule614, - rule615, - rule616, - rule617, - rule618, - rule619, - rule620, - rule621, - rule622, - rule623, - rule624, - rule625, - rule626, - rule627, - rule628, - rule629, - rule630, - rule631, - rule632, - rule633, - rule634, - rule635, - rule636, - rule637, - rule638, - rule639, - rule640, - rule641, - rule642, - rule643, - rule644, - rule645, - rule646, - rule647, - rule648, - rule649, - rule650, - rule651, - rule652, - rule653, - rule654, - rule655, - rule656, - rule657, - rule658, - rule659, - rule660, - rule661, - rule662, - rule663, - rule664, - rule665, - rule666, - rule667, - rule668, - rule669, - rule670, - rule671, - rule672, - rule673, - rule674, - rule675, - rule676, - rule677, - rule678, - rule679, - rule680, - rule681, - rule682, - rule683, - rule684, - rule685, - rule686, - rule687, - rule688, - rule689, - rule690, - rule691, - rule692, - rule693, - rule694, - rule695, - rule696, - rule697, - rule698, - rule699, - rule700, - rule701, - rule702, - rule703, - rule704, - rule705, - rule706, -]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts deleted file mode 100644 index e1b55ca9124b1..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/types.ts +++ /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 type { Readable } from 'stream'; - -import type { SavedObjectsClientContract } from '@kbn/core/server'; -import type { SanitizedRule } from '@kbn/alerting-plugin/common'; -import type { RulesClient, PartialRule } from '@kbn/alerting-plugin/server'; -import { ruleTypeMappings } from '@kbn/securitysolution-rules'; - -import type { - FieldsOrUndefined, - Id, - IdOrUndefined, - PageOrUndefined, - PerPageOrUndefined, - QueryFilterOrUndefined, - RuleIdOrUndefined, - SortFieldOrUndefined, - SortOrderOrUndefined, -} from '../../../../common/detection_engine/schemas/common'; - -import type { CreateRulesSchema } from '../../../../common/detection_engine/schemas/request/rule_schemas'; -import type { PatchRulesSchema } from '../../../../common/detection_engine/schemas/request/patch_rules_schema'; -import type { UpdateRulesSchema } from '../../../../common/detection_engine/schemas/request'; - -import type { RuleParams } from '../schemas/rule_schemas'; -import type { IRuleExecutionLogForRoutes } from '../rule_monitoring'; - -export type RuleAlertType = SanitizedRule<RuleParams>; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export interface IRuleAssetSOAttributes extends Record<string, any> { - rule_id: string | null | undefined; - version: string | null | undefined; - name: string | null | undefined; -} - -export interface IRuleAssetSavedObject { - type: string; - id: string; - attributes: IRuleAssetSOAttributes; -} - -export interface HapiReadableStream extends Readable { - hapi: { - filename: string; - }; -} - -export interface Clients { - rulesClient: RulesClient; -} - -export const isAlertType = ( - partialAlert: PartialRule<RuleParams> -): partialAlert is RuleAlertType => { - const ruleTypeValues = Object.values(ruleTypeMappings) as unknown as string[]; - return ruleTypeValues.includes(partialAlert.alertTypeId as string); -}; - -export interface CreateRulesOptions<T extends CreateRulesSchema = CreateRulesSchema> { - rulesClient: RulesClient; - params: T; - id?: string; - immutable?: boolean; - defaultEnabled?: boolean; -} - -export interface UpdateRulesOptions { - rulesClient: RulesClient; - existingRule: RuleAlertType | null | undefined; - ruleUpdate: UpdateRulesSchema; -} - -export interface PatchRulesOptions { - rulesClient: RulesClient; - nextParams: PatchRulesSchema; - existingRule: RuleAlertType | null | undefined; -} - -export interface ReadRuleOptions { - rulesClient: RulesClient; - id: IdOrUndefined; - ruleId: RuleIdOrUndefined; -} - -export interface DeleteRuleOptions { - ruleId: Id; - rulesClient: RulesClient; - ruleExecutionLog: IRuleExecutionLogForRoutes; -} - -export interface FindRuleOptions { - rulesClient: RulesClient; - filter: QueryFilterOrUndefined; - fields: FieldsOrUndefined; - sortField: SortFieldOrUndefined; - sortOrder: SortOrderOrUndefined; - page: PageOrUndefined; - perPage: PerPageOrUndefined; -} - -export interface LegacyMigrateParams { - rulesClient: RulesClient; - savedObjectsClient: SavedObjectsClientContract; - rule: SanitizedRule<RuleParams> | null | undefined; -} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts deleted file mode 100644 index dc35948bafe8a..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.ts +++ /dev/null @@ -1,312 +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 { RuleAction, RuleNotifyWhenType, SanitizedRule } from '@kbn/alerting-plugin/common'; -import type { RulesClient } from '@kbn/alerting-plugin/server'; -import type { SavedObjectReference } from '@kbn/core/server'; -import { isEmpty } from 'lodash/fp'; -import { - NOTIFICATION_THROTTLE_NO_ACTIONS, - NOTIFICATION_THROTTLE_RULE, -} from '../../../../common/constants'; -import type { - AddPrepackagedRulesSchema, - FullResponseSchema, -} from '../../../../common/detection_engine/schemas/request'; -import { transformAlertToRuleAction } from '../../../../common/detection_engine/transform_actions'; -import { withSecuritySpan } from '../../../utils/with_security_span'; -// eslint-disable-next-line no-restricted-imports -import { legacyRuleActionsSavedObjectType } from '../rule_actions/legacy_saved_object_mappings'; -// eslint-disable-next-line no-restricted-imports -import type { - LegacyIRuleActionsAttributes, - LegacyRuleActions, - LegacyRuleAlertSavedObjectAction, -} from '../rule_actions/legacy_types'; -import type { RuleParams } from '../schemas/rule_schemas'; -import type { LegacyMigrateParams, RuleAlertType } from './types'; - -/** - * Given a throttle from a "security_solution" rule this will transform it into an "alerting" notifyWhen - * on their saved object. - * @params throttle The throttle from a "security_solution" rule - * @returns The correct "NotifyWhen" for a Kibana alerting. - */ -export const transformToNotifyWhen = ( - throttle: string | null | undefined -): RuleNotifyWhenType | null => { - if (throttle == null || throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { - return null; // Although I return null, this does not change the value of the "notifyWhen" and it keeps the current value of "notifyWhen" - } else if (throttle === NOTIFICATION_THROTTLE_RULE) { - return 'onActiveAlert'; - } else { - return 'onThrottleInterval'; - } -}; - -/** - * Given a throttle from a "security_solution" rule this will transform it into an "alerting" "throttle" - * on their saved object. - * @params throttle The throttle from a "security_solution" rule - * @returns The "alerting" throttle - */ -export const transformToAlertThrottle = (throttle: string | null | undefined): string | null => { - if ( - throttle == null || - throttle === NOTIFICATION_THROTTLE_RULE || - throttle === NOTIFICATION_THROTTLE_NO_ACTIONS - ) { - return null; - } else { - return throttle; - } -}; - -/** - * Given a set of actions from an "alerting" Saved Object (SO) this will transform it into a "security_solution" alert action. - * If this detects any legacy rule actions it will transform it. If both are sent in which is not typical but possible due to - * the split nature of the API's this will prefer the usage of the non-legacy version. Eventually the "legacyRuleActions" should - * be removed. - * @param alertAction The alert action form a "alerting" Saved Object (SO). - * @param legacyRuleActions Legacy "side car" rule actions that if it detects it being passed it in will transform using it. - * @returns The actions of the FullResponseSchema - */ -export const transformActions = ( - alertAction: RuleAction[] | undefined, - legacyRuleActions: LegacyRuleActions | null | undefined -): FullResponseSchema['actions'] => { - if (alertAction != null && alertAction.length !== 0) { - return alertAction.map((action) => transformAlertToRuleAction(action)); - } else if (legacyRuleActions != null) { - return legacyRuleActions.actions; - } else { - return []; - } -}; - -/** - * Given a throttle from an "alerting" Saved Object (SO) this will transform it into a "security_solution" - * throttle type. If given the "legacyRuleActions" but we detect that the rule for an unknown reason has actions - * on it to which should not be typical but possible due to the split nature of the API's, this will prefer the - * usage of the non-legacy version. Eventually the "legacyRuleActions" should be removed. - * @param throttle The throttle from a "alerting" Saved Object (SO) - * @param legacyRuleActions Legacy "side car" rule actions that if it detects it being passed it in will transform using it. - * @returns The "security_solution" throttle - */ -export const transformFromAlertThrottle = ( - rule: SanitizedRule<RuleParams>, - legacyRuleActions: LegacyRuleActions | null | undefined -): string => { - if (legacyRuleActions == null || (rule.actions != null && rule.actions.length > 0)) { - if (rule.muteAll || rule.actions.length === 0) { - return NOTIFICATION_THROTTLE_NO_ACTIONS; - } else if ( - rule.notifyWhen === 'onActiveAlert' || - (rule.throttle == null && rule.notifyWhen == null) - ) { - return NOTIFICATION_THROTTLE_RULE; - } else if (rule.throttle == null) { - return NOTIFICATION_THROTTLE_NO_ACTIONS; - } else { - return rule.throttle; - } - } else { - return legacyRuleActions.ruleThrottle; - } -}; - -/** - * Mutes, unmutes, or does nothing to the alert if no changed is detected - * @param id The id of the alert to (un)mute - * @param rulesClient the rules client - * @param muteAll If the existing alert has all actions muted - * @param throttle If the existing alert has a throttle set - */ -export const maybeMute = async ({ - id, - rulesClient, - muteAll, - throttle, -}: { - id: SanitizedRule['id']; - rulesClient: RulesClient; - muteAll: SanitizedRule<RuleParams>['muteAll']; - throttle: string | null | undefined; -}): Promise<void> => { - if (muteAll && throttle !== NOTIFICATION_THROTTLE_NO_ACTIONS) { - await rulesClient.unmuteAll({ id }); - } else if (!muteAll && throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { - await rulesClient.muteAll({ id }); - } else { - // Do nothing, no-operation - } -}; - -/** - * Translate legacy action sidecar action to rule action - */ -export const getUpdatedActionsParams = ({ - rule, - ruleThrottle, - actions, - references, -}: { - rule: SanitizedRule<RuleParams>; - ruleThrottle: string | null; - actions: LegacyRuleAlertSavedObjectAction[]; - references: SavedObjectReference[]; -}): Omit<SanitizedRule<RuleParams>, 'id'> => { - const { id, ...restOfRule } = rule; - - const actionReference = references.reduce<Record<string, SavedObjectReference>>( - (acc, reference) => { - acc[reference.name] = reference; - return acc; - }, - {} - ); - - if (isEmpty(actionReference)) { - throw new Error( - `An error occurred migrating legacy action for rule with id:${id}. Connector reference id not found.` - ); - } - // If rule has an action on any other interval (other than on every - // rule run), need to move the action info from the sidecar/legacy action - // into the rule itself - return { - ...restOfRule, - actions: actions.reduce<RuleAction[]>((acc, action) => { - const { actionRef, action_type_id: actionTypeId, ...resOfAction } = action; - if (!actionReference[actionRef]) { - return acc; - } - return [ - ...acc, - { - ...resOfAction, - id: actionReference[actionRef].id, - actionTypeId, - }, - ]; - }, []), - throttle: transformToAlertThrottle(ruleThrottle), - notifyWhen: transformToNotifyWhen(ruleThrottle), - }; -}; - -/** - * Determines if rule needs to be migrated from legacy actions - * and returns necessary pieces for the updated rule - */ -export const legacyMigrate = async ({ - rulesClient, - savedObjectsClient, - rule, -}: LegacyMigrateParams): Promise<SanitizedRule<RuleParams> | null | undefined> => - withSecuritySpan('legacyMigrate', async () => { - if (rule == null || rule.id == null) { - return rule; - } - /** - * On update / patch I'm going to take the actions as they are, better off taking rules client.find (siem.notification) result - * and putting that into the actions array of the rule, then set the rules onThrottle property, notifyWhen and throttle from null -> actual value (1hr etc..) - * Then use the rules client to delete the siem.notification - * Then with the legacy Rule Actions saved object type, just delete it. - */ - // find it using the references array, not params.ruleAlertId - const [siemNotification, legacyRuleActionsSO] = await Promise.all([ - rulesClient.find({ - options: { - filter: 'alert.attributes.alertTypeId:(siem.notifications)', - hasReference: { - type: 'alert', - id: rule.id, - }, - }, - }), - savedObjectsClient.find<LegacyIRuleActionsAttributes>({ - type: legacyRuleActionsSavedObjectType, - hasReference: { - type: 'alert', - id: rule.id, - }, - }), - ]); - - const siemNotificationsExist = siemNotification != null && siemNotification.data.length > 0; - const legacyRuleNotificationSOsExist = - legacyRuleActionsSO != null && legacyRuleActionsSO.saved_objects.length > 0; - - // Assumption: if no legacy sidecar SO or notification rule types exist - // that reference the rule in question, assume rule actions are not legacy - if (!siemNotificationsExist && !legacyRuleNotificationSOsExist) { - return rule; - } - // If the legacy notification rule type ("siem.notification") exist, - // migration and cleanup are needed - if (siemNotificationsExist) { - await rulesClient.delete({ id: siemNotification.data[0].id }); - } - // If legacy notification sidecar ("siem-detection-engine-rule-actions") - // exist, migration and cleanup are needed - if (legacyRuleNotificationSOsExist) { - // Delete the legacy sidecar SO - await savedObjectsClient.delete( - legacyRuleActionsSavedObjectType, - legacyRuleActionsSO.saved_objects[0].id - ); - - // If "siem-detection-engine-rule-actions" notes that `ruleThrottle` is - // "no_actions" or "rule", rule has no actions or rule is set to run - // action on every rule run. In these cases, sidecar deletion is the only - // cleanup needed and updates to the "throttle" and "notifyWhen". "siem.notification" are - // not created for these action types - if ( - legacyRuleActionsSO.saved_objects[0].attributes.ruleThrottle === 'no_actions' || - legacyRuleActionsSO.saved_objects[0].attributes.ruleThrottle === 'rule' - ) { - return rule; - } - - // Use "legacyRuleActionsSO" instead of "siemNotification" as "siemNotification" is not created - // until a rule is run and added to task manager. That means that if by chance a user has a rule - // with actions which they have yet to enable, the actions would be lost. Instead, - // "legacyRuleActionsSO" is created on rule creation (pre 7.15) and we can rely on it to be there - const migratedRule = getUpdatedActionsParams({ - rule, - ruleThrottle: legacyRuleActionsSO.saved_objects[0].attributes.ruleThrottle, - actions: legacyRuleActionsSO.saved_objects[0].attributes.actions, - references: legacyRuleActionsSO.saved_objects[0].references, - }); - - await rulesClient.update({ - id: rule.id, - data: migratedRule, - }); - - return { id: rule.id, ...migratedRule }; - } - }); - -/** - * Converts an array of prepackaged rules to a Map with rule IDs as keys - * - * @param rules Array of prepackaged rules - * @returns Map - */ -export const prepackagedRulesToMap = (rules: AddPrepackagedRulesSchema[]) => - new Map(rules.map((rule) => [rule.rule_id, rule])); - -/** - * Converts an array of rules to a Map with rule IDs as keys - * - * @param rules Array of rules - * @returns Map - */ -export const rulesToMap = (rules: RuleAlertType[]) => - new Map(rules.map((rule) => [rule.params.ruleId, rule])); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_ndjson_prepackaged_rules.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_ndjson_prepackaged_rules.sh index 2c983fd7c9824..c69916770086c 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_ndjson_prepackaged_rules.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/create_ndjson_prepackaged_rules.sh @@ -8,10 +8,10 @@ # i=0; -for f in ../rules/prepackaged_rules/*.json ; do +for f in ../prebuilt_rules/content/prepackaged_rules/*.json ; do ((i++)); echo "converting $f" - cat ../rules/prepackaged_rules/windows_msxsl_network.json | tr -d '\n' | tr -d ' ' >> pre_packaged_rules.ndjson + cat ../prebuilt_rules/content/prepackaged_rules/windows_msxsl_network.json | tr -d '\n' | tr -d ' ' >> pre_packaged_rules.ndjson echo "" >> pre_packaged_rules.ndjson done echo "{\"exported_count\":$i,\"missing_rules\":[],\"missing_rules_count\":0}" >> pre_packaged_rules.ndjson diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/regen_prepackage_rules_index.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/regen_prepackage_rules_index.sh index 507e9815c4c74..6eb1b28dc8c2e 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/regen_prepackage_rules_index.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/regen_prepackage_rules_index.sh @@ -4,7 +4,7 @@ set -e # Regenerates the index.ts that contains all of the rules that are read in from json -PREPACKAGED_RULES_INDEX=../rules/prepackaged_rules/index.ts +PREPACKAGED_RULES_INDEX=../prebuilt_rules/content/prepackaged_rules/index.ts echo "/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one @@ -18,7 +18,7 @@ echo "/* " > ${PREPACKAGED_RULES_INDEX} RULE_NUMBER=1 -for f in ../rules/prepackaged_rules/*.json ; do +for f in ../prebuilt_rules/content/prepackaged_rules/*.json ; do echo "import rule${RULE_NUMBER} from './$(basename -- "$f")';" >> ${PREPACKAGED_RULES_INDEX} RULE_NUMBER=$[$RULE_NUMBER +1] done @@ -26,7 +26,7 @@ done echo "export const rawRules = [" >> ${PREPACKAGED_RULES_INDEX} RULE_NUMBER=1 -for f in ../rules/prepackaged_rules/*.json ; do +for f in ../prebuilt_rules/content/prepackaged_rules/*.json ; do echo " rule${RULE_NUMBER}," >> ${PREPACKAGED_RULES_INDEX} RULE_NUMBER=$[$RULE_NUMBER +1] done diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/add_prepackaged_timelines.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/add_prepackaged_timelines.sh index 6de63e5910a37..48bd884294611 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/add_prepackaged_timelines.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/add_prepackaged_timelines.sh @@ -11,9 +11,9 @@ set -e ./check_env_variables.sh # Uses a defaults if no argument is specified -TIMELINES=${1:-../../rules/prepackaged_timelines/index.ndjson} +TIMELINES=${1:-../../prebuilt_rules/content/prepackaged_timelines/index.ndjson} -# Example to import and overwrite everything from ../rules/prepackaged_timelines/index.ndjson +# Example to import and overwrite everything from ../prebuilt_rules/content/prepackaged_timelines/index.ndjson # ./timelines/add_prepackaged_timelines.sh curl -s -k \ -H 'kbn-xsrf: 123' \ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/regen_prepackage_timelines_index.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/regen_prepackage_timelines_index.sh index 8bd6101dfcc9a..a643a7bbcf838 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/regen_prepackage_timelines_index.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/timelines/regen_prepackage_timelines_index.sh @@ -4,7 +4,7 @@ set -e ./check_env_variables.sh # Regenerates the index.ts that contains all of the timelines that are read in from json -PREPACKAGED_TIMELINES_INDEX=../rules/prepackaged_timelines/index.ndjson +PREPACKAGED_TIMELINES_INDEX=../prebuilt_rules/content/prepackaged_timelines/index.ndjson # Clear existing content echo "" > ${PREPACKAGED_TIMELINES_INDEX} @@ -20,7 +20,7 @@ echo "/* // Do not hand edit. Run that script to regenerate package information instead " > ${PREPACKAGED_TIMELINES_INDEX} -for f in ../rules/prepackaged_timelines/*.json ; do +for f in ../prebuilt_rules/content/prepackaged_timelines/*.json ; do echo "converting $f" sed ':a;N;$!ba;s/\n/ /g' $f >> ${PREPACKAGED_TIMELINES_INDEX} done diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts index e3b05756709cc..a5a8c4963f227 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts @@ -8,10 +8,9 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isEmpty } from 'lodash'; import type { Filter } from '@kbn/es-query'; import type { - FiltersOrUndefined, - TimestampOverrideOrUndefined, + RuleFilterArray, TimestampOverride, -} from '../../../../common/detection_engine/schemas/common/schemas'; +} from '../../../../common/detection_engine/rule_schema'; import { getQueryFilter } from './get_query_filter'; interface BuildEventsSearchQuery { @@ -25,7 +24,7 @@ interface BuildEventsSearchQuery { sortOrder?: estypes.SortOrder; searchAfterSortIds: estypes.SortResults | undefined; primaryTimestamp: TimestampOverride; - secondaryTimestamp: TimestampOverrideOrUndefined; + secondaryTimestamp: TimestampOverride | undefined; trackTotalHits?: boolean; } @@ -35,9 +34,9 @@ interface BuildEqlSearchRequestParams { from: string; to: string; size: number; - filters: FiltersOrUndefined; + filters: RuleFilterArray | undefined; primaryTimestamp: TimestampOverride; - secondaryTimestamp: TimestampOverrideOrUndefined; + secondaryTimestamp: TimestampOverride | undefined; runtimeMappings: estypes.MappingRuntimeFields | undefined; eventCategoryOverride?: string; timestampField?: string; @@ -54,7 +53,7 @@ const buildTimeRangeFilter = ({ to: string; from: string; primaryTimestamp: TimestampOverride; - secondaryTimestamp: TimestampOverrideOrUndefined; + secondaryTimestamp: TimestampOverride | undefined; }): estypes.QueryDslQueryContainer => { // The primaryTimestamp is always provided and will contain either the timestamp override field or `@timestamp` otherwise. // The secondaryTimestamp is `undefined` if diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts index f9a4c21898b09..7f565036abe3f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_ml_signals.ts @@ -16,7 +16,7 @@ import type { import type { GenericBulkCreateResponse } from '../rule_types/factories'; import type { Anomaly } from '../../machine_learning'; import type { BulkCreate, WrapHits } from './types'; -import type { CompleteRule, MachineLearningRuleParams } from '../schemas/rule_schemas'; +import type { CompleteRule, MachineLearningRuleParams } from '../rule_schema'; import { buildReasonMessageForMlAlert } from './reason_formatters'; import type { BaseFieldsLatest } from '../../../../common/detection_engine/schemas/alerts'; import type { IRuleExecutionLogForExecutors } from '../rule_monitoring'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts index 93e37c41c8ab3..fb0920bef9ade 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts @@ -12,8 +12,8 @@ import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; import { getIndexVersion } from '../../routes/index/get_index_version'; import { SIGNALS_TEMPLATE_VERSION } from '../../routes/index/get_signals_template'; -import type { EqlRuleParams } from '../../schemas/rule_schemas'; -import { getCompleteRuleMock, getEqlRuleParams } from '../../schemas/rule_schemas.mock'; +import type { EqlRuleParams } from '../../rule_schema'; +import { getCompleteRuleMock, getEqlRuleParams } from '../../rule_schema/mocks'; import { ruleExecutionLogMock } from '../../rule_monitoring/mocks'; import { eqlExecutor } from './eql'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts index 436ebec9c27c1..a67a404478b18 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts @@ -32,7 +32,7 @@ import { getUnprocessedExceptionsWarnings, } from '../utils'; import { buildReasonMessageForEqlAlert } from '../reason_formatters'; -import type { CompleteRule, EqlRuleParams } from '../../schemas/rule_schemas'; +import type { CompleteRule, EqlRuleParams } from '../../rule_schema'; import { withSecuritySpan } from '../../../../utils/with_security_span'; import type { BaseFieldsLatest, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts index 49e2ef43282a2..ac6865554b36a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.test.ts @@ -9,12 +9,12 @@ import dateMath from '@kbn/datemath'; import type { RuleExecutorServicesMock } from '@kbn/alerting-plugin/server/mocks'; import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; import { mlExecutor } from './ml'; -import { getCompleteRuleMock, getMlRuleParams } from '../../schemas/rule_schemas.mock'; +import { getCompleteRuleMock, getMlRuleParams } from '../../rule_schema/mocks'; import { getListClientMock } from '@kbn/lists-plugin/server/services/lists/list_client.mock'; import { findMlSignals } from '../find_ml_signals'; import { bulkCreateMlSignals } from '../bulk_create_ml_signals'; import { mlPluginServerMock } from '@kbn/ml-plugin/server/mocks'; -import type { MachineLearningRuleParams } from '../../schemas/rule_schemas'; +import type { MachineLearningRuleParams } from '../../rule_schema'; import { ruleExecutionLogMock } from '../../rule_monitoring/mocks'; jest.mock('../find_ml_signals'); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts index ad49900078c4c..b7cb97f906bf0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/ml.ts @@ -15,7 +15,7 @@ import type { import type { ListClient } from '@kbn/lists-plugin/server'; import type { Filter } from '@kbn/es-query'; import { isJobStarted } from '../../../../../common/machine_learning/helpers'; -import type { CompleteRule, MachineLearningRuleParams } from '../../schemas/rule_schemas'; +import type { CompleteRule, MachineLearningRuleParams } from '../../rule_schema'; import { bulkCreateMlSignals } from '../bulk_create_ml_signals'; import { filterEventsAgainstList } from '../filters/filter_events_against_list'; import { findMlSignals } from '../find_ml_signals'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts index b2309bee8ad0a..52c0247b950db 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/query.ts @@ -21,7 +21,7 @@ import { getFilter } from '../get_filter'; import { searchAfterAndBulkCreate } from '../search_after_bulk_create'; import type { RuleRangeTuple, BulkCreate, WrapHits } from '../types'; import type { ITelemetryEventsSender } from '../../../telemetry/sender'; -import type { CompleteRule, UnifiedQueryRuleParams } from '../../schemas/rule_schemas'; +import type { CompleteRule, UnifiedQueryRuleParams } from '../../rule_schema'; import type { ExperimentalFeatures } from '../../../../../common/experimental_features'; import { buildReasonMessageForQueryAlert } from '../reason_formatters'; import { withSecuritySpan } from '../../../../utils/with_security_span'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts index 4f99412acb5c2..8ebf39867dea4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threat_match.ts @@ -18,7 +18,7 @@ import type { Filter } from '@kbn/es-query'; import type { RuleRangeTuple, BulkCreate, WrapHits } from '../types'; import type { ITelemetryEventsSender } from '../../../telemetry/sender'; import { createThreatSignals } from '../threat_mapping/create_threat_signals'; -import type { CompleteRule, ThreatRuleParams } from '../../schemas/rule_schemas'; +import type { CompleteRule, ThreatRuleParams } from '../../rule_schema'; import { withSecuritySpan } from '../../../../utils/with_security_span'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts index 832ffa1e5b5c4..0e780d9709ab8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.test.ts @@ -11,10 +11,10 @@ import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas import type { RuleExecutorServicesMock } from '@kbn/alerting-plugin/server/mocks'; import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; import { thresholdExecutor } from './threshold'; -import { getThresholdRuleParams, getCompleteRuleMock } from '../../schemas/rule_schemas.mock'; +import { getThresholdRuleParams, getCompleteRuleMock } from '../../rule_schema/mocks'; import { sampleEmptyAggsSearchResults } from '../__mocks__/es_results'; import { getThresholdTermsHash } from '../utils'; -import type { ThresholdRuleParams } from '../../schemas/rule_schemas'; +import type { ThresholdRuleParams } from '../../rule_schema'; import { createRuleDataClientMock } from '@kbn/rule-registry-plugin/server/rule_data_client/rule_data_client.mock'; import { TIMESTAMP } from '@kbn/rule-data-utils'; import { ruleExecutionLogMock } from '../../rule_monitoring/mocks'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts index ed82f7cdb1f8a..515caf5dcd5e1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/threshold.ts @@ -16,7 +16,7 @@ import type { } from '@kbn/alerting-plugin/server'; import type { IRuleDataReader } from '@kbn/rule-registry-plugin/server'; import type { Filter } from '@kbn/es-query'; -import type { CompleteRule, ThresholdRuleParams } from '../../schemas/rule_schemas'; +import type { CompleteRule, ThresholdRuleParams } from '../../rule_schema'; import { getFilter } from '../get_filter'; import { bulkCreateThresholdSignals, @@ -92,7 +92,7 @@ export const thresholdExecutor = async ({ : await getThresholdSignalHistory({ from: tuple.from.toISOString(), to: tuple.to.toISOString(), - ruleId: ruleParams.ruleId, + frameworkRuleId: completeRule.alertId, bucketByFields: ruleParams.threshold.field, ruleDataReader, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts index 521fdf1e5a595..33d710ed2ffa4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_filter.ts @@ -18,11 +18,8 @@ import type { } from '@kbn/alerting-plugin/server'; import type { Filter } from '@kbn/es-query'; import { assertUnreachable } from '../../../../common/utility_types'; -import type { - QueryOrUndefined, - SavedIdOrUndefined, - IndexOrUndefined, -} from '../../../../common/detection_engine/schemas/common/schemas'; +import type { IndexPatternArray, RuleQuery } from '../../../../common/detection_engine/rule_schema'; +import type { SavedIdOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; import type { PartialFilter } from '../types'; import { withSecuritySpan } from '../../../utils/with_security_span'; import type { ESBoolQuery } from '../../../../common/typed_json'; @@ -32,10 +29,10 @@ interface GetFilterArgs { type: Type; filters: unknown | undefined; language: LanguageOrUndefined; - query: QueryOrUndefined; + query: RuleQuery | undefined; savedId: SavedIdOrUndefined; services: RuleExecutorServices<AlertInstanceState, AlertInstanceContext, 'default'>; - index: IndexOrUndefined; + index: IndexPatternArray | undefined; exceptionFilter: Filter | undefined; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts index 58b13152bb64a..15477388839e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_query_filter.ts @@ -9,7 +9,7 @@ import type { Language } from '@kbn/securitysolution-io-ts-alerting-types'; import type { Filter, EsQueryConfig, DataViewBase } from '@kbn/es-query'; import { buildEsQuery } from '@kbn/es-query'; import type { ESBoolQuery } from '../../../../common/typed_json'; -import type { Index, Query } from '../../../../common/detection_engine/schemas/common'; +import type { IndexPatternArray, RuleQuery } from '../../../../common/detection_engine/rule_schema'; export const getQueryFilter = ({ query, @@ -18,10 +18,10 @@ export const getQueryFilter = ({ index, exceptionFilter, }: { - query: Query; + query: RuleQuery; language: Language; filters: unknown; - index: Index; + index: IndexPatternArray; exceptionFilter: Filter | undefined; }): ESBoolQuery => { const indexPattern: DataViewBase = { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.test.ts index ffd9e2b46d48f..5ae4aff3a6687 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.test.ts @@ -5,10 +5,7 @@ * 2.0. */ -import type { - RiskScore, - RiskScoreMappingOrUndefined, -} from '@kbn/securitysolution-io-ts-alerting-types'; +import type { RiskScore, RiskScoreMapping } from '@kbn/securitysolution-io-ts-alerting-types'; import { sampleDocRiskScore } from '../__mocks__/es_results'; import type { BuildRiskScoreFromMappingReturn } from './build_risk_score_from_mapping'; import { buildRiskScoreFromMapping } from './build_risk_score_from_mapping'; @@ -187,7 +184,7 @@ describe('buildRiskScoreFromMapping', () => { interface TestCase { fieldValue: unknown; scoreDefault: RiskScore; - scoreMapping: RiskScoreMappingOrUndefined; + scoreMapping: RiskScoreMapping | undefined; expected: BuildRiskScoreFromMappingReturn; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts index 9e875165cf469..e17c5b941cbb5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_risk_score_from_mapping.ts @@ -5,23 +5,21 @@ * 2.0. */ -import type { - RiskScore, - RiskScoreMappingOrUndefined, -} from '@kbn/securitysolution-io-ts-alerting-types'; import { get } from 'lodash/fp'; -import type { Meta } from '../../../../../common/detection_engine/schemas/common/schemas'; + +import type { RiskScore, RiskScoreMapping } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { RuleMetadata } from '../../../../../common/detection_engine/rule_schema'; import type { SignalSource } from '../types'; export interface BuildRiskScoreFromMappingProps { eventSource: SignalSource; riskScore: RiskScore; - riskScoreMapping: RiskScoreMappingOrUndefined; + riskScoreMapping: RiskScoreMapping | undefined; } export interface BuildRiskScoreFromMappingReturn { riskScore: RiskScore; - riskScoreMeta: Meta; // TODO: Stricter types + riskScoreMeta: RuleMetadata; // TODO: Stricter types } /** diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_rule_name_from_mapping.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_rule_name_from_mapping.ts index ae7247c01b1af..933a330a77098 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_rule_name_from_mapping.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_rule_name_from_mapping.ts @@ -7,22 +7,23 @@ import * as t from 'io-ts'; import { get } from 'lodash/fp'; + import type { - Meta, - Name, - RuleNameOverrideOrUndefined, -} from '../../../../../common/detection_engine/schemas/common/schemas'; + RuleMetadata, + RuleName, + RuleNameOverride, +} from '../../../../../common/detection_engine/rule_schema'; import type { SignalSource } from '../types'; interface BuildRuleNameFromMappingProps { eventSource: SignalSource; - ruleName: Name; - ruleNameMapping: RuleNameOverrideOrUndefined; + ruleName: RuleName; + ruleNameMapping: RuleNameOverride | undefined; } interface BuildRuleNameFromMappingReturn { - ruleName: Name; - ruleNameMeta: Meta; // TODO: Stricter types + ruleName: RuleName; + ruleNameMeta: RuleMetadata; // TODO: Stricter types } export const buildRuleNameFromMapping = ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts index 75e53cdf0ae4c..875da0124b42f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.test.ts @@ -5,11 +5,7 @@ * 2.0. */ -import type { - Severity, - SeverityMappingOrUndefined, -} from '@kbn/securitysolution-io-ts-alerting-types'; - +import type { Severity, SeverityMapping } from '@kbn/securitysolution-io-ts-alerting-types'; import { sampleDocSeverity } from '../__mocks__/es_results'; import type { BuildSeverityFromMappingReturn } from './build_severity_from_mapping'; import { buildSeverityFromMapping } from './build_severity_from_mapping'; @@ -141,7 +137,7 @@ interface TestCase { fieldName?: string; fieldValue: unknown; severityDefault: Severity; - severityMapping: SeverityMappingOrUndefined; + severityMapping: SeverityMapping | undefined; expected: BuildSeverityFromMappingReturn; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts index 4a57467040b5e..53691eeb98814 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/mappings/build_severity_from_mapping.ts @@ -7,25 +7,25 @@ import { get } from 'lodash/fp'; +import { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import type { - Severity, + SeverityMapping, SeverityMappingItem, - SeverityMappingOrUndefined, } from '@kbn/securitysolution-io-ts-alerting-types'; -import { severity as SeverityIOTS } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { Meta } from '../../../../../common/detection_engine/schemas/common/schemas'; + +import type { RuleMetadata } from '../../../../../common/detection_engine/rule_schema'; import type { SearchTypes } from '../../../../../common/detection_engine/types'; import type { SignalSource } from '../types'; export interface BuildSeverityFromMappingProps { eventSource: SignalSource; severity: Severity; - severityMapping: SeverityMappingOrUndefined; + severityMapping: SeverityMapping | undefined; } export interface BuildSeverityFromMappingReturn { severity: Severity; - severityMeta: Meta; // TODO: Stricter types + severityMeta: RuleMetadata; // TODO: Stricter types } const severitySortMapping = { @@ -66,7 +66,7 @@ export const buildSeverityFromMapping = ({ } }); - if (severityMatch != null && SeverityIOTS.is(severityMatch.severity)) { + if (severityMatch != null && Severity.is(severityMatch.severity)) { return overriddenSeverity(severityMatch.severity, severityMatch.field); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts index a3cf4ebc95d7c..1b947b1be08a2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/preview/alert_instance_factory_stub.ts @@ -11,7 +11,7 @@ import type { RuleTypeState, } from '@kbn/alerting-plugin/common'; import { Alert } from '@kbn/alerting-plugin/server/alert'; -import type { RuleParams } from '../../schemas/rule_schemas'; +import type { RuleParams } from '../../rule_schema'; export const alertInstanceFactoryStub = < TParams extends RuleParams, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_data_view.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_data_view.ts index 3cbe4d4b1c980..293d853f1d2ad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_data_view.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_data_view.ts @@ -12,7 +12,7 @@ import type { QueryRuleParams, ThreatRuleParams, ThresholdRuleParams, -} from '../../schemas/rule_schemas'; +} from '../../rule_schema'; /** * This extracts the "dataViewId" and returns it as a saved object reference. diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts index 68d262961bfa1..db86d90598be7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts @@ -5,13 +5,14 @@ * 2.0. */ -import { extractExceptionsList } from './extract_exceptions_list'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import type { RuleParams } from '../../schemas/rule_schemas'; import { EXCEPTION_LIST_NAMESPACE, EXCEPTION_LIST_NAMESPACE_AGNOSTIC, } from '@kbn/securitysolution-list-constants'; + +import type { RuleParams } from '../../rule_schema'; +import { extractExceptionsList } from './extract_exceptions_list'; import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants'; describe('extract_exceptions_list', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts index 87e607bb3b9a1..010fd04923159 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts @@ -7,7 +7,7 @@ import type { Logger, SavedObjectReference } from '@kbn/core/server'; import { getSavedObjectType } from '@kbn/securitysolution-list-utils'; -import type { RuleParams } from '../../schemas/rule_schemas'; +import type { RuleParams } from '../../rule_schema'; import { getSavedObjectNamePatternForExceptionsList } from './utils'; /** diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts index 76325e187b005..5d27540773641 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts @@ -6,12 +6,13 @@ */ import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { extractReferences } from './extract_references'; -import type { RuleParams } from '../../schemas/rule_schemas'; import { EXCEPTION_LIST_NAMESPACE, EXCEPTION_LIST_NAMESPACE_AGNOSTIC, } from '@kbn/securitysolution-list-constants'; + +import type { RuleParams } from '../../rule_schema'; +import { extractReferences } from './extract_references'; import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants'; describe('extract_references', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.ts index 9843223b37e89..91ceb32edfe35 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.ts @@ -8,7 +8,7 @@ import type { Logger } from '@kbn/core/server'; import type { RuleParamsAndRefs } from '@kbn/alerting-plugin/server'; -import type { RuleParams } from '../../schemas/rule_schemas'; +import type { RuleParams } from '../../rule_schema'; import { isMachineLearningParams } from '../utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts index 4d086970e458e..d653591493a3a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts @@ -8,8 +8,9 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import type { SavedObjectReference } from '@kbn/core/server'; import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants'; + +import type { RuleParams } from '../../rule_schema'; import { injectExceptionsReferences } from './inject_exceptions_list'; -import type { RuleParams } from '../../schemas/rule_schemas'; import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants'; describe('inject_exceptions_list', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.ts index 84a2c30c2e7ee..3baab73f1e50f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.ts @@ -6,7 +6,7 @@ */ import type { Logger, SavedObjectReference } from '@kbn/core/server'; -import type { RuleParams } from '../../schemas/rule_schemas'; +import type { RuleParams } from '../../rule_schema'; import { getSavedObjectReferenceForExceptionsList, logMissingSavedObjectError } from './utils'; /** diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts index 3d7b17e5b6e89..f0dba726c712c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts @@ -8,8 +8,9 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import type { SavedObjectReference } from '@kbn/core/server'; import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants'; + +import type { RuleParams } from '../../rule_schema'; import { injectReferences } from './inject_references'; -import type { RuleParams } from '../../schemas/rule_schemas'; import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants'; describe('inject_references', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.ts index bae0e0749a62b..40ad3cf3678a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.ts @@ -6,7 +6,7 @@ */ import type { Logger, SavedObjectReference } from '@kbn/core/server'; -import type { RuleParams } from '../../schemas/rule_schemas'; +import type { RuleParams } from '../../rule_schema'; import { isMachineLearningParams } from '../utils'; import { injectExceptionsReferences } from './inject_exceptions_list'; import { injectDataViewReferences } from './inject_data_view'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.ts index 203d6ce342d4c..3293508e035c4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/log_missing_saved_object_error.ts @@ -6,7 +6,7 @@ */ import type { Logger } from '@kbn/core/server'; -import type { RuleParams } from '../../../schemas/rule_schemas'; +import type { RuleParams } from '../../../rule_schema'; type Keys = keyof RuleParams; type PossibleRuleParamValues = RuleParams[Keys]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 613104bc7604b..489237ea1b2c9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -23,12 +23,12 @@ import type { SearchListItemArraySchema } from '@kbn/securitysolution-io-ts-list import { getSearchListItemResponseMock } from '@kbn/lists-plugin/common/schemas/response/search_list_item_schema.mock'; import { getRuleRangeTuples } from './utils'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { getCompleteRuleMock, getQueryRuleParams } from '../schemas/rule_schemas.mock'; +import { getCompleteRuleMock, getQueryRuleParams } from '../rule_schema/mocks'; import { bulkCreateFactory } from '../rule_types/factories/bulk_create_factory'; import { wrapHitsFactory } from '../rule_types/factories/wrap_hits_factory'; import { ruleExecutionLogMock } from '../rule_monitoring/mocks'; import type { BuildReasonMessage } from './reason_formatters'; -import type { QueryRuleParams } from '../schemas/rule_schemas'; +import type { QueryRuleParams } from '../rule_schema'; import { createPersistenceServicesMock } from '@kbn/rule-registry-plugin/server/utils/create_persistence_rule_type_wrapper.mock'; import type { PersistenceServices } from '@kbn/rule-registry-plugin/server'; import { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts index 0a0534e887c5e..04fec0e21a467 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts @@ -14,10 +14,7 @@ import type { import type { SignalSearchResponse, SignalSource } from './types'; import { buildEventsSearchQuery } from './build_events_query'; import { createErrorsFromShard, makeFloatString } from './utils'; -import type { - TimestampOverride, - TimestampOverrideOrUndefined, -} from '../../../../common/detection_engine/schemas/common/schemas'; +import type { TimestampOverride } from '../../../../common/detection_engine/rule_schema'; import { withSecuritySpan } from '../../../utils/with_security_span'; import type { IRuleExecutionLogForExecutors } from '../rule_monitoring'; @@ -33,7 +30,7 @@ interface SingleSearchAfterParams { sortOrder?: estypes.SortOrder; filter: estypes.QueryDslQueryContainer; primaryTimestamp: TimestampOverride; - secondaryTimestamp: TimestampOverrideOrUndefined; + secondaryTimestamp: TimestampOverride | undefined; trackTotalHits?: boolean; runtimeMappings: estypes.MappingRuntimeFields | undefined; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts index f9ce4479dc3ac..03175fa0dda11 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/types.ts @@ -35,7 +35,7 @@ import type { SignalsEnrichment, WrapHits, } from '../types'; -import type { CompleteRule, ThreatRuleParams } from '../../schemas/rule_schemas'; +import type { CompleteRule, ThreatRuleParams } from '../../rule_schema'; import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring'; export type SortOrderOrUndefined = 'asc' | 'desc' | undefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/__snapshots__/get_threshold_signal_history.test.ts.snap b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/__snapshots__/get_threshold_signal_history.test.ts.snap index 005ce07afcac4..bb9e29f1f5b52 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/__snapshots__/get_threshold_signal_history.test.ts.snap +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/__snapshots__/get_threshold_signal_history.test.ts.snap @@ -17,7 +17,7 @@ Object { }, Object { "term": Object { - "signal.rule.rule_id": "threshold-rule", + "kibana.alert.rule.uuid": "threshold-rule", }, }, Object { @@ -91,7 +91,7 @@ Object { }, Object { "term": Object { - "signal.rule.rule_id": "threshold-rule", + "kibana.alert.rule.uuid": "threshold-rule", }, }, Object { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_threshold_aggregation.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_threshold_aggregation.ts index f0b808e743772..1bc2b18860722 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_threshold_aggregation.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/build_threshold_aggregation.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ThresholdNormalized } from '../../../../../common/detection_engine/schemas/common'; +import type { ThresholdNormalized } from '../../../../../common/detection_engine/rule_schema'; import { shouldFilterByCardinality } from './utils'; export const buildThresholdMultiBucketAggregation = ({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.test.ts index d92bd5b360b2d..1fa0baaedfa13 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ThresholdNormalized } from '../../../../../common/detection_engine/schemas/common/schemas'; +import type { ThresholdNormalized } from '../../../../../common/detection_engine/rule_schema'; import { calculateThresholdSignalUuid } from '../utils'; import { getTransformedHits } from './bulk_create_threshold_signals'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts index 758e69553bb4b..22ebcf8c1ced0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/bulk_create_threshold_signals.ts @@ -12,12 +12,12 @@ import type { AlertInstanceState, RuleExecutorServices, } from '@kbn/alerting-plugin/server'; -import type { ThresholdNormalized } from '../../../../../common/detection_engine/schemas/common/schemas'; +import type { ThresholdNormalized } from '../../../../../common/detection_engine/rule_schema'; import type { GenericBulkCreateResponse } from '../../rule_types/factories/bulk_create_factory'; import { calculateThresholdSignalUuid } from '../utils'; import { buildReasonMessageForThresholdAlert } from '../reason_formatters'; import type { ThresholdSignalHistory, BulkCreate, WrapHits } from '../types'; -import type { CompleteRule, ThresholdRuleParams } from '../../schemas/rule_schemas'; +import type { CompleteRule, ThresholdRuleParams } from '../../rule_schema'; import type { BaseFieldsLatest } from '../../../../../common/detection_engine/schemas/alerts'; import type { ThresholdBucket } from './types'; import { createEnrichEventsFunction } from '../enrichments'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts index 63e293401a77a..dba72e0560734 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/find_threshold_signals.ts @@ -17,8 +17,7 @@ import type { ESBoolQuery } from '../../../../../common/typed_json'; import type { ThresholdNormalized, TimestampOverride, - TimestampOverrideOrUndefined, -} from '../../../../../common/detection_engine/schemas/common/schemas'; +} from '../../../../../common/detection_engine/rule_schema'; import { singleSearchAfter } from '../single_search_after'; import { buildThresholdMultiBucketAggregation, @@ -43,7 +42,7 @@ interface FindThresholdSignalsParams { threshold: ThresholdNormalized; runtimeMappings: estypes.MappingRuntimeFields | undefined; primaryTimestamp: TimestampOverride; - secondaryTimestamp: TimestampOverrideOrUndefined; + secondaryTimestamp: TimestampOverride | undefined; aggregatableTimestampField: string; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.test.ts index d9db2972a89d3..0e896ad49cd82 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.test.ts @@ -12,10 +12,10 @@ describe('buildPreviousThresholdAlertRequest', () => { const bucketByFields: string[] = []; const to = 'now'; const from = 'now-6m'; - const ruleId = 'threshold-rule'; + const frameworkRuleId = 'threshold-rule'; expect( - buildPreviousThresholdAlertRequest({ from, to, ruleId, bucketByFields }) + buildPreviousThresholdAlertRequest({ from, to, frameworkRuleId, bucketByFields }) ).toMatchSnapshot(); }); @@ -23,10 +23,10 @@ describe('buildPreviousThresholdAlertRequest', () => { const bucketByFields: string[] = ['host.name', 'user.name']; const to = 'now'; const from = 'now-6m'; - const ruleId = 'threshold-rule'; + const frameworkRuleId = 'threshold-rule'; expect( - buildPreviousThresholdAlertRequest({ from, to, ruleId, bucketByFields }) + buildPreviousThresholdAlertRequest({ from, to, frameworkRuleId, bucketByFields }) ).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts index 997e6c213f3b1..4826cd574a906 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/get_threshold_signal_history.ts @@ -7,6 +7,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { IRuleDataReader } from '@kbn/rule-registry-plugin/server'; +import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; import type { ThresholdSignalHistory } from '../types'; import { buildThresholdSignalHistory } from './build_signal_history'; import { createErrorsFromShard } from '../utils'; @@ -14,7 +15,7 @@ import { createErrorsFromShard } from '../utils'; interface GetThresholdSignalHistoryParams { from: string; to: string; - ruleId: string; + frameworkRuleId: string; bucketByFields: string[]; ruleDataReader: IRuleDataReader; } @@ -22,7 +23,7 @@ interface GetThresholdSignalHistoryParams { export const getThresholdSignalHistory = async ({ from, to, - ruleId, + frameworkRuleId, bucketByFields, ruleDataReader, }: GetThresholdSignalHistoryParams): Promise<{ @@ -32,7 +33,7 @@ export const getThresholdSignalHistory = async ({ const request = buildPreviousThresholdAlertRequest({ from, to, - ruleId, + frameworkRuleId, bucketByFields, }); @@ -48,12 +49,12 @@ export const getThresholdSignalHistory = async ({ export const buildPreviousThresholdAlertRequest = ({ from, to, - ruleId, + frameworkRuleId, bucketByFields, }: { from: string; to: string; - ruleId: string; + frameworkRuleId: string; bucketByFields: string[]; }): estypes.SearchRequest => { return { @@ -80,7 +81,7 @@ export const buildPreviousThresholdAlertRequest = ({ }, { term: { - 'signal.rule.rule_id': ruleId, + [ALERT_RULE_UUID]: frameworkRuleId, }, }, // We might find a signal that was generated on the interval for old data... make sure to exclude those. diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/utils.ts index bcb61f04cda0d..1e1ecbc12b444 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold/utils.ts @@ -8,7 +8,7 @@ import type { ThresholdNormalized, ThresholdWithCardinality, -} from '../../../../../common/detection_engine/schemas/common'; +} from '../../../../../common/detection_engine/rule_schema'; export const shouldFilterByCardinality = ( threshold: ThresholdNormalized diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index f786a28b50044..e8dd19fd2ff8e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -26,7 +26,7 @@ import type { EqlSequence, } from '../../../../common/detection_engine/types'; import type { ITelemetryEventsSender } from '../../telemetry/sender'; -import type { RuleParams } from '../schemas/rule_schemas'; +import type { RuleParams } from '../rule_schema'; import type { GenericBulkCreateResponse } from '../rule_types/factories'; import type { BuildReasonMessage } from './reason_formatters'; import type { @@ -35,7 +35,7 @@ import type { WrappedFieldsLatest, } from '../../../../common/detection_engine/schemas/alerts'; import type { IRuleExecutionLogForExecutors } from '../rule_monitoring'; -import type { FullResponseSchema } from '../../../../common/detection_engine/schemas/request'; +import type { RuleResponse } from '../../../../common/detection_engine/rule_schema'; import type { EnrichEvents } from './enrichments/types'; export interface ThresholdResult { @@ -187,7 +187,7 @@ export interface Signal { _meta?: { version: number; }; - rule: FullResponseSchema; + rule: RuleResponse; /** * @deprecated Use "parents" instead of "parent" */ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 24b5b068d5689..c8a98aeabd82a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -31,10 +31,8 @@ import type { } from '@kbn/alerting-plugin/server'; import { parseDuration } from '@kbn/alerting-plugin/server'; import type { ExceptionListClient, ListClient, ListPluginSetup } from '@kbn/lists-plugin/server'; -import type { - TimestampOverride, - Privilege, -} from '../../../../common/detection_engine/schemas/common'; +import type { TimestampOverride } from '../../../../common/detection_engine/rule_schema'; +import type { Privilege } from '../../../../common/detection_engine/schemas/common'; import { RuleExecutionStatus } from '../../../../common/detection_engine/rule_monitoring'; import type { BulkResponseErrorAggregation, @@ -57,7 +55,7 @@ import type { RuleParams, ThreatRuleParams, ThresholdRuleParams, -} from '../schemas/rule_schemas'; +} from '../rule_schema'; import type { BaseHit, SearchTypes } from '../../../../common/detection_engine/types'; import type { IRuleExecutionLogForExecutors } from '../rule_monitoring'; import { withSecuritySpan } from '../../../utils/with_security_span'; diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/__mocks__/authz.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/__mocks__/authz.ts new file mode 100644 index 0000000000000..33ab06ba67a8f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/__mocks__/authz.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 { mlPluginServerMock } from '@kbn/ml-plugin/server/mocks'; + +export const mlServicesMock = mlPluginServerMock; + +const mockValidateRuleType = jest.fn().mockResolvedValue({ valid: true, message: undefined }); + +export const buildMlAuthz = jest.fn().mockReturnValue({ validateRuleType: mockValidateRuleType }); diff --git a/x-pack/plugins/security_solution/server/lib/machine_learning/mocks.ts b/x-pack/plugins/security_solution/server/lib/machine_learning/mocks.ts index b76dba6d072ef..2a46163515958 100644 --- a/x-pack/plugins/security_solution/server/lib/machine_learning/mocks.ts +++ b/x-pack/plugins/security_solution/server/lib/machine_learning/mocks.ts @@ -8,13 +8,3 @@ import { mlPluginServerMock } from '@kbn/ml-plugin/server/mocks'; export const mlServicesMock = mlPluginServerMock; - -const mockValidateRuleType = jest.fn().mockResolvedValue({ valid: true, message: undefined }); -const createBuildMlAuthzMock = () => - jest.fn().mockReturnValue({ validateRuleType: mockValidateRuleType }); - -export const mlAuthzMock = { - create: () => ({ - buildMlAuthz: createBuildMlAuthzMock(), - }), -}; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts index 64bee51ae8965..9d32145717de6 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/__mocks__/request_responses.ts @@ -41,7 +41,9 @@ export const getExportTimelinesRequest = () => }); export const getImportTimelinesRequest = async (fileName?: string) => { - const dir = resolve(join(__dirname, '../../detection_engine/rules/prepackaged_timelines')); + const dir = resolve( + join(__dirname, '../../detection_engine/prebuilt_rules/content/prepackaged_timelines') + ); const file = fileName ?? 'index.ndjson'; const dataPath = path.join(dir, file); const readable = await getReadables(dataPath); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts index 69d1b672ddcf4..b003a54657feb 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/helpers.ts @@ -25,7 +25,10 @@ export const installPrepackagedTimelines = async ( ): Promise<ImportTimelineResultSchema | Error> => { let readStream; const dir = resolve( - join(__dirname, filePath ?? '../../../../detection_engine/rules/prepackaged_timelines') + join( + __dirname, + filePath ?? '../../../../detection_engine/prebuilt_rules/content/prepackaged_timelines' + ) ); const file = fileName ?? 'index.ndjson'; const dataPath = path.join(dir, file); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts b/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts index f742a5c6171c0..bf260085b5a75 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/utils/check_timelines_status.ts @@ -66,7 +66,10 @@ export const checkTimelinesStatus = async ( timeline: TimelineSavedObject[]; }; const dir = resolve( - join(__dirname, filePath ?? '../../detection_engine/rules/prepackaged_timelines') + join( + __dirname, + filePath ?? '../../detection_engine/prebuilt_rules/content/prepackaged_timelines' + ) ); const file = fileName ?? 'index.ndjson'; const dataPath = path.join(dir, file); diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts index 7759caa20e1f9..2ff4a663560b7 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts @@ -48,8 +48,16 @@ export class EventFilterValidator extends BaseValidator { return item.listId === ENDPOINT_EVENT_FILTERS_LIST_ID; } + protected async validateHasWritePrivilege(): Promise<void> { + return super.validateHasPrivilege('canWriteEventFilters'); + } + + protected async validateHasReadPrivilege(): Promise<void> { + return super.validateHasPrivilege('canReadEventFilters'); + } + async validatePreCreateItem(item: CreateExceptionListItemOptions) { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); await this.validateEventFilterData(item); // user can always create a global entry so additional checks not needed @@ -67,7 +75,7 @@ export class EventFilterValidator extends BaseValidator { ): Promise<UpdateExceptionListItemOptions> { const updatedItem = _updatedItem as ExceptionItemLikeOptions; - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); await this.validateEventFilterData(updatedItem); try { @@ -96,27 +104,27 @@ export class EventFilterValidator extends BaseValidator { } async validatePreGetOneItem(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreSummary(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreDeleteItem(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); } async validatePreExport(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); } async validatePreSingleListFind(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreMultiListFind(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreImport(): Promise<void> { diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts index 39e86e7104fae..01809c2c28f68 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts @@ -60,9 +60,18 @@ export class HostIsolationExceptionsValidator extends BaseValidator { return item.listId === ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID; } + protected async validateHasWritePrivilege(): Promise<void> { + return super.validateHasPrivilege('canWriteHostIsolationExceptions'); + } + + protected async validateHasReadPrivilege(): Promise<void> { + return super.validateHasPrivilege('canReadHostIsolationExceptions'); + } + async validatePreCreateItem( item: CreateExceptionListItemOptions ): Promise<CreateExceptionListItemOptions> { + await this.validateHasWritePrivilege(); await this.validateCanIsolateHosts(); await this.validateHostIsolationData(item); await this.validateByPolicyItem(item); @@ -75,6 +84,7 @@ export class HostIsolationExceptionsValidator extends BaseValidator { ): Promise<UpdateExceptionListItemOptions> { const updatedItem = _updatedItem as ExceptionItemLikeOptions; + await this.validateHasWritePrivilege(); await this.validateCanIsolateHosts(); await this.validateHostIsolationData(updatedItem); await this.validateByPolicyItem(updatedItem); @@ -83,27 +93,27 @@ export class HostIsolationExceptionsValidator extends BaseValidator { } async validatePreGetOneItem(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreSummary(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreDeleteItem(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); } async validatePreExport(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); } async validatePreSingleListFind(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreMultiListFind(): Promise<void> { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreImport(): Promise<void> { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index a71048462b5e6..dd4993807f7c3 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -80,9 +80,10 @@ import type { CreateQueryRuleAdditionalOptions, } from './lib/detection_engine/rule_types/types'; // eslint-disable-next-line no-restricted-imports -import { legacyRulesNotificationAlertType } from './lib/detection_engine/notifications/legacy_rules_notification_alert_type'; -// eslint-disable-next-line no-restricted-imports -import { legacyIsNotificationAlertExecutor } from './lib/detection_engine/notifications/legacy_types'; +import { + legacyRulesNotificationAlertType, + legacyIsNotificationAlertExecutor, +} from './lib/detection_engine/rule_actions_legacy'; import { createSecurityRuleTypeWrapper } from './lib/detection_engine/rule_types/create_security_rule_type_wrapper'; import { RequestContextFactory } from './request_context_factory'; @@ -342,7 +343,8 @@ export class Plugin implements ISecuritySolutionPlugin { const securitySolutionSearchStrategy = securitySolutionSearchStrategyProvider( depsStart.data, endpointContext, - depsStart.spaces?.spacesService?.getSpaceId + depsStart.spaces?.spacesService?.getSpaceId, + ruleDataClient ); plugins.data.search.registerSearchStrategy( diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index f9d2177aa9e10..df67c6ddce423 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -10,14 +10,17 @@ import type { IRuleDataClient, RuleDataPluginService } from '@kbn/rule-registry- import type { SecuritySolutionPluginRouter } from '../types'; -import { createRulesRoute } from '../lib/detection_engine/routes/rules/create_rules_route'; +import { registerFleetIntegrationsRoutes } from '../lib/detection_engine/fleet_integrations'; +import { registerPrebuiltRulesRoutes } from '../lib/detection_engine/prebuilt_rules'; +// eslint-disable-next-line no-restricted-imports +import { registerLegacyRuleActionsRoutes } from '../lib/detection_engine/rule_actions_legacy'; +import { registerRuleExceptionsRoutes } from '../lib/detection_engine/rule_exceptions'; +import { registerRuleManagementRoutes } from '../lib/detection_engine/rule_management'; +import { registerRuleMonitoringRoutes } from '../lib/detection_engine/rule_monitoring'; +import { registerRulePreviewRoutes } from '../lib/detection_engine/rule_preview'; + import { createIndexRoute } from '../lib/detection_engine/routes/index/create_index_route'; import { readIndexRoute } from '../lib/detection_engine/routes/index/read_index_route'; -import { readRulesRoute } from '../lib/detection_engine/routes/rules/read_rules_route'; -import { findRulesRoute } from '../lib/detection_engine/routes/rules/find_rules_route'; -import { deleteRulesRoute } from '../lib/detection_engine/routes/rules/delete_rules_route'; -import { updateRulesRoute } from '../lib/detection_engine/routes/rules/update_rules_route'; -import { patchRulesRoute } from '../lib/detection_engine/routes/rules/patch_rules_route'; import { createSignalsMigrationRoute } from '../lib/detection_engine/routes/signals/create_signals_migration_route'; import { deleteSignalsMigrationRoute } from '../lib/detection_engine/routes/signals/delete_signals_migration_route'; import { finalizeSignalsMigrationRoute } from '../lib/detection_engine/routes/signals/finalize_signals_migration_route'; @@ -25,18 +28,7 @@ import { getSignalsMigrationStatusRoute } from '../lib/detection_engine/routes/s import { querySignalsRoute } from '../lib/detection_engine/routes/signals/query_signals_route'; import { setSignalsStatusRoute } from '../lib/detection_engine/routes/signals/open_close_signals_route'; import { deleteIndexRoute } from '../lib/detection_engine/routes/index/delete_index_route'; -import { readTagsRoute } from '../lib/detection_engine/routes/tags/read_tags_route'; import { readPrivilegesRoute } from '../lib/detection_engine/routes/privileges/read_privileges_route'; -import { addPrepackedRulesRoute } from '../lib/detection_engine/routes/rules/add_prepackaged_rules_route'; -import { createRulesBulkRoute } from '../lib/detection_engine/routes/rules/create_rules_bulk_route'; -import { updateRulesBulkRoute } from '../lib/detection_engine/routes/rules/update_rules_bulk_route'; -import { patchRulesBulkRoute } from '../lib/detection_engine/routes/rules/patch_rules_bulk_route'; -import { deleteRulesBulkRoute } from '../lib/detection_engine/routes/rules/delete_rules_bulk_route'; -import { performBulkActionRoute } from '../lib/detection_engine/routes/rules/perform_bulk_action_route'; -import { importRulesRoute } from '../lib/detection_engine/routes/rules/import_rules_route'; -import { exportRulesRoute } from '../lib/detection_engine/routes/rules/export_rules_route'; -import { registerRuleMonitoringRoutes } from '../lib/detection_engine/rule_monitoring'; -import { getPrepackagedRulesStatusRoute } from '../lib/detection_engine/routes/rules/get_prepackaged_rules_status_route'; import { createTimelinesRoute, deleteTimelinesRoute, @@ -59,21 +51,15 @@ import type { SetupPlugins, StartPlugins } from '../plugin'; import type { ConfigType } from '../config'; import type { ITelemetryEventsSender } from '../lib/telemetry/sender'; import { installPrepackedTimelinesRoute } from '../lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; -import { previewRulesRoute } from '../lib/detection_engine/routes/rules/preview_rules_route'; import type { CreateRuleOptions, CreateSecurityRuleTypeWrapperProps, } from '../lib/detection_engine/rule_types/types'; -// eslint-disable-next-line no-restricted-imports -import { legacyCreateLegacyNotificationRoute } from '../lib/detection_engine/routes/rules/legacy_create_legacy_notification'; import { createSourcererDataViewRoute, getSourcererDataViewRoute } from '../lib/sourcerer/routes'; import type { ITelemetryReceiver } from '../lib/telemetry/receiver'; import { telemetryDetectionRulesPreviewRoute } from '../lib/detection_engine/routes/telemetry/telemetry_detection_rules_preview_route'; import { readAlertsIndexExistsRoute } from '../lib/detection_engine/routes/index/read_alerts_index_exists_route'; -import { getInstalledIntegrationsRoute } from '../lib/detection_engine/routes/fleet/get_installed_integrations/get_installed_integrations_route'; import { registerResolverRoutes } from '../endpoint/routes/resolver'; -import { findRuleExceptionReferencesRoute } from '../lib/detection_engine/routes/rules/find_rule_exceptions_route'; -import { createRuleExceptionsRoute } from '../lib/detection_engine/routes/rules/create_rule_exceptions_route'; import { createEsIndexRoute, createPrebuiltSavedObjectsRoute, @@ -86,6 +72,7 @@ import { readPrebuiltDevToolContentRoute, restartTransformRoute, } from '../lib/risk_score/routes'; + export const initRoutes = ( router: SecuritySolutionPluginRouter, config: ConfigType, @@ -102,15 +89,13 @@ export const initRoutes = ( previewRuleDataClient: IRuleDataClient, previewTelemetryReceiver: ITelemetryReceiver ) => { - // Detection Engine Rule routes that have the REST endpoints of /api/detection_engine/rules - // All REST rule creation, deletion, updating, etc - createRulesRoute(router, ml); - readRulesRoute(router, logger); - updateRulesRoute(router, ml); - patchRulesRoute(router, ml); - deleteRulesRoute(router); - findRulesRoute(router, logger); - previewRulesRoute( + registerFleetIntegrationsRoutes(router, logger); + registerLegacyRuleActionsRoutes(router, logger); + registerPrebuiltRulesRoutes(router, config, security); + registerRuleExceptionsRoutes(router); + registerRuleManagementRoutes(router, config, ml, logger); + registerRuleMonitoringRoutes(router); + registerRulePreviewRoutes( router, config, ml, @@ -121,29 +106,11 @@ export const initRoutes = ( getStartServices, logger ); - createRuleExceptionsRoute(router); - - // Once we no longer have the legacy notifications system/"side car actions" this should be removed. - legacyCreateLegacyNotificationRoute(router, logger); - - addPrepackedRulesRoute(router); - getPrepackagedRulesStatusRoute(router, config, security); - createRulesBulkRoute(router, ml, logger); - updateRulesBulkRoute(router, ml, logger); - patchRulesBulkRoute(router, ml, logger); - deleteRulesBulkRoute(router, logger); - performBulkActionRoute(router, ml, logger); - registerResolverRoutes(router, getStartServices, config); - - registerRuleMonitoringRoutes(router); - getInstalledIntegrationsRoute(router, logger); + registerResolverRoutes(router, getStartServices, config); createTimelinesRoute(router, config, security); patchTimelinesRoute(router, config, security); - importRulesRoute(router, config, ml); - exportRulesRoute(router, config, logger); - findRuleExceptionReferencesRoute(router); importTimelinesRoute(router, config, security); exportTimelinesRoute(router, config, security); @@ -177,9 +144,6 @@ export const initRoutes = ( readAlertsIndexExistsRoute(router); deleteIndexRoute(router); - // Detection Engine tags routes that have the REST endpoints of /api/detection_engine/tags - readTagsRoute(router); - // Privileges API to get the generic user privileges readPrivilegesRoute(router, hasEncryptionKey); diff --git a/x-pack/plugins/security_solution/server/saved_objects.ts b/x-pack/plugins/security_solution/server/saved_objects.ts index 225c0ad453a3d..bc1d86add3767 100644 --- a/x-pack/plugins/security_solution/server/saved_objects.ts +++ b/x-pack/plugins/security_solution/server/saved_objects.ts @@ -9,9 +9,9 @@ import type { CoreSetup } from '@kbn/core/server'; import { noteType, pinnedEventType, timelineType } from './lib/timeline/saved_object_mappings'; // eslint-disable-next-line no-restricted-imports -import { legacyType as legacyRuleActionsType } from './lib/detection_engine/rule_actions/legacy_saved_object_mappings'; +import { legacyType as legacyRuleActionsType } from './lib/detection_engine/rule_actions_legacy'; import { ruleExecutionType } from './lib/detection_engine/rule_monitoring'; -import { ruleAssetType } from './lib/detection_engine/rules/rule_asset/rule_asset_saved_object_mappings'; +import { ruleAssetType } from './lib/detection_engine/prebuilt_rules/logic/rule_asset/rule_asset_saved_object_mappings'; import { type as signalsMigrationType } from './lib/detection_engine/migrations/saved_objects'; import { exceptionsArtifactType, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts index 4208ea44937bc..7abc747eb0c82 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts @@ -8,6 +8,7 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; import type { HostsRequestOptions } from '../../../../../../common/search_strategy/security_solution'; +import { RiskScoreEntity } from '../../../../../../common/search_strategy/security_solution'; import * as buildQuery from './query.all_hosts.dsl'; import * as buildRiskQuery from '../../risk_score/all/query.risk_score.dsl'; import { allHosts } from '.'; @@ -128,6 +129,7 @@ describe('allHosts search strategy', () => { expect(buildHostsRiskQuery).toHaveBeenCalledWith({ defaultIndex: ['ml_host_risk_score_latest_test-space'], filterQuery: { terms: { 'host.name': [hostName] } }, + riskScoreEntity: RiskScoreEntity.host, }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts index fb4a4aa27353b..86a3cfae8b4f3 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts @@ -19,7 +19,11 @@ import type { } from '../../../../../../common/search_strategy/security_solution/hosts'; import type { HostRiskScore } from '../../../../../../common/search_strategy'; -import { getHostRiskIndex, buildHostNamesFilter } from '../../../../../../common/search_strategy'; +import { + RiskScoreEntity, + getHostRiskIndex, + buildHostNamesFilter, +} from '../../../../../../common/search_strategy'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import type { SecuritySolutionFactory } from '../../types'; @@ -116,6 +120,7 @@ async function getHostRiskData( buildRiskScoreQuery({ defaultIndex: [getHostRiskIndex(spaceId)], filterQuery: buildHostNamesFilter(hostNames), + riskScoreEntity: RiskScoreEntity.host, }) ); return hostRiskResponse; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.ts new file mode 100644 index 0000000000000..58b2a55bc1594 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.test.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. + */ + +import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { riskScore } from '.'; +import type { IEsSearchResponse } from '@kbn/data-plugin/public'; +import { allowedExperimentalValues } from '../../../../../../common/experimental_features'; +import type { + HostRiskScore, + RiskScoreRequestOptions, +} from '../../../../../../common/search_strategy'; +import { RiskScoreEntity, RiskSeverity } from '../../../../../../common/search_strategy'; +import type { EndpointAppContextService } from '../../../../../endpoint/endpoint_app_context_services'; +import type { EndpointAppContext } from '../../../../../endpoint/types'; +import * as buildQuery from './query.risk_score.dsl'; +import { get } from 'lodash/fp'; +import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks'; +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; + +export const mockSearchStrategyResponse: IEsSearchResponse<HostRiskScore> = { + rawResponse: { + took: 1, + timed_out: false, + _shards: { + total: 2, + successful: 2, + skipped: 1, + failed: 0, + }, + hits: { + max_score: null, + hits: [ + { + _id: '4', + _index: 'index', + _source: { + '@timestamp': '1234567899', + host: { + name: 'testUsermame', + risk: { + rule_risks: [], + calculated_level: RiskSeverity.high, + calculated_score_norm: 75, + multipliers: [], + }, + }, + }, + }, + ], + }, + }, + isPartial: false, + isRunning: false, + total: 2, + loaded: 2, +}; + +const searchMock = jest.fn(); + +const mockDeps = { + esClient: {} as IScopedClusterClient, + ruleDataClient: { + ...(ruleRegistryMocks.createRuleDataClient('.alerts-security.alerts') as IRuleDataClient), + getReader: jest.fn((_options?: { namespace?: string }) => ({ + search: searchMock, + getDynamicIndexPattern: jest.fn(), + })), + }, + savedObjectsClient: {} as SavedObjectsClientContract, + endpointContext: { + logFactory: { + get: jest.fn().mockReturnValue({ + warn: jest.fn(), + }), + }, + config: jest.fn().mockResolvedValue({}), + experimentalFeatures: { + ...allowedExperimentalValues, + }, + service: {} as EndpointAppContextService, + } as EndpointAppContext, + request: {} as KibanaRequest, +}; + +export const mockOptions: RiskScoreRequestOptions = { + defaultIndex: ['logs-*'], + riskScoreEntity: RiskScoreEntity.host, + includeAlertsCount: true, +}; + +describe('buildRiskScoreQuery search strategy', () => { + const buildKpiRiskScoreQuery = jest.spyOn(buildQuery, 'buildRiskScoreQuery'); + + describe('buildDsl', () => { + test('should build dsl query', () => { + riskScore.buildDsl(mockOptions); + expect(buildKpiRiskScoreQuery).toHaveBeenCalledWith(mockOptions); + }); + }); + + test('should not enhance data when includeAlertsCount is false', async () => { + const result = await riskScore.parse( + { ...mockOptions, includeAlertsCount: false }, + mockSearchStrategyResponse, + mockDeps + ); + + expect(get('data[0].alertsCount', result)).toBeUndefined(); + }); + + test('should enhance data with alerts count', async () => { + const alertsCunt = 9999; + searchMock.mockReturnValue({ + aggregations: { + alertsByEntity: { + buckets: [ + { + key: 'testUsermame', + doc_count: alertsCunt, + }, + ], + }, + }, + }); + + const result = await riskScore.parse(mockOptions, mockSearchStrategyResponse, mockDeps); + + expect(get('data[0].alertsCount', result)).toBe(alertsCunt); + }); +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts index 0d2c01b735a5f..5e46ac2b4f440 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/risk_score/all/index.ts @@ -5,12 +5,19 @@ * 2.0. */ -import type { IEsSearchResponse } from '@kbn/data-plugin/common'; +import type { IEsSearchResponse, SearchRequest } from '@kbn/data-plugin/common'; +import { get, getOr } from 'lodash/fp'; + +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { SecuritySolutionFactory } from '../../types'; import type { RiskScoreRequestOptions, RiskQueries, + BucketItem, + HostRiskScore, + UserRiskScore, } from '../../../../../../common/search_strategy'; +import { RiskScoreEntity } from '../../../../../../common/search_strategy'; import { inspectStringifyObject } from '../../../../../utils/build_query'; import { buildRiskScoreQuery } from './query.risk_score.dsl'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; @@ -26,7 +33,14 @@ export const riskScore: SecuritySolutionFactory< return buildRiskScoreQuery(options); }, - parse: async (options: RiskScoreRequestOptions, response: IEsSearchResponse) => { + parse: async ( + options: RiskScoreRequestOptions, + response: IEsSearchResponse, + deps?: { + spaceId?: string; + ruleDataClient?: IRuleDataClient | null; + } + ) => { const inspect = { dsl: [inspectStringifyObject(buildRiskScoreQuery(options))], }; @@ -34,11 +48,65 @@ export const riskScore: SecuritySolutionFactory< const totalCount = getTotalCount(response.rawResponse.hits.total); const hits = response?.rawResponse?.hits?.hits; const data = hits?.map((hit) => hit._source) ?? []; + const nameField = options.riskScoreEntity === RiskScoreEntity.host ? 'host.name' : 'user.name'; + const names = data.map((risk) => get(nameField, risk) ?? ''); + + const enhancedData = + deps && options.includeAlertsCount + ? await enhanceData(data, names, nameField, deps.ruleDataClient, deps.spaceId) + : data; + return { ...response, inspect, totalCount, - data, + data: enhancedData, }; }, }; + +async function enhanceData( + data: Array<HostRiskScore | UserRiskScore>, + names: string[], + nameField: string, + ruleDataClient?: IRuleDataClient | null, + spaceId?: string +): Promise<Array<HostRiskScore | UserRiskScore>> { + const ruleDataReader = ruleDataClient?.getReader({ namespace: spaceId }); + const query = getAlertsQueryForEntity(names, nameField); + + const response = await ruleDataReader?.search(query); + const buckets: BucketItem[] = getOr([], 'aggregations.alertsByEntity.buckets', response); + + const alertsCountByEntityName: Record<string, number> = buckets.reduce( + (acc, { key, doc_count: count }) => ({ + ...acc, + [key]: count, + }), + {} + ); + + return data.map((risk) => ({ + ...risk, + alertsCount: alertsCountByEntityName[get(nameField, risk)] ?? 0, + })); +} + +const getAlertsQueryForEntity = (names: string[], nameField: string): SearchRequest => ({ + size: 0, + query: { + bool: { + filter: [ + { term: { 'kibana.alert.workflow_status': 'open' } }, + { terms: { [nameField]: names } }, + ], + }, + }, + aggs: { + alertsByEntity: { + terms: { + field: nameField, + }, + }, + }, +}); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts index 51529bc21d801..f222e2130ee26 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts @@ -11,6 +11,7 @@ import type { SavedObjectsClientContract, } from '@kbn/core/server'; import type { IEsSearchResponse, ISearchRequestParams } from '@kbn/data-plugin/common'; +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { FactoryQueryTypes, StrategyRequestType, @@ -29,6 +30,7 @@ export interface SecuritySolutionFactory<T extends FactoryQueryTypes> { endpointContext: EndpointAppContext; request: KibanaRequest; spaceId?: string; + ruleDataClient?: IRuleDataClient | null; } ) => Promise<StrategyResponseType<T>>; } diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts index 1f17bbfc870fc..18bc75edb5304 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.test.ts @@ -14,6 +14,7 @@ import type { UsersRequestOptions } from '../../../../../../common/search_strate import * as buildRiskQuery from '../../risk_score/all/query.risk_score.dsl'; import { get } from 'lodash/fp'; +import { RiskScoreEntity } from '../../../../../../common/search_strategy'; class IndexNotFoundException extends Error { meta: { body: { error: { type: string } } }; @@ -115,6 +116,7 @@ describe('allHosts search strategy', () => { expect(buildHostsRiskQuery).toHaveBeenCalledWith({ defaultIndex: ['ml_user_risk_score_latest_test-space'], filterQuery: { terms: { 'user.name': userName } }, + riskScoreEntity: RiskScoreEntity.user, }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts index d5c13e0d2b52e..c936ad85f79e0 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts @@ -23,7 +23,11 @@ import type { import type { AllUsersAggEsItem } from '../../../../../../common/search_strategy/security_solution/users/common'; import { buildRiskScoreQuery } from '../../risk_score/all/query.risk_score.dsl'; import type { RiskSeverity, UserRiskScore } from '../../../../../../common/search_strategy'; -import { buildUserNamesFilter, getUserRiskIndex } from '../../../../../../common/search_strategy'; +import { + RiskScoreEntity, + buildUserNamesFilter, + getUserRiskIndex, +} from '../../../../../../common/search_strategy'; export const allUsers: SecuritySolutionFactory<UsersQueries.users> = { buildDsl: (options: UsersRequestOptions) => { @@ -123,6 +127,7 @@ async function getUserRiskData( buildRiskScoreQuery({ defaultIndex: [getUserRiskIndex(spaceId)], filterQuery: buildUserNamesFilter(userNames), + riskScoreEntity: RiskScoreEntity.user, }) ); return userRiskResponse; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts index faee7d6b6d8f3..1acb6687b8ace 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts @@ -10,6 +10,7 @@ import type { ISearchStrategy, PluginStart } from '@kbn/data-plugin/server'; import { shimHitsTotal } from '@kbn/data-plugin/server'; import { ENHANCED_ES_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; import type { KibanaRequest } from '@kbn/core/server'; +import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; import type { FactoryQueryTypes, StrategyResponseType, @@ -33,7 +34,8 @@ function assertValidRequestType<T extends FactoryQueryTypes>( export const securitySolutionSearchStrategyProvider = <T extends FactoryQueryTypes>( data: PluginStart, endpointContext: EndpointAppContext, - getSpaceId?: (request: KibanaRequest) => string + getSpaceId?: (request: KibanaRequest) => string, + ruleDataClient?: IRuleDataClient | null ): ISearchStrategy<StrategyRequestType<T>, StrategyResponseType<T>> => { const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY); @@ -60,6 +62,7 @@ export const securitySolutionSearchStrategyProvider = <T extends FactoryQueryTyp endpointContext, request: deps.request, spaceId: getSpaceId && getSpaceId(deps.request), + ruleDataClient, }) ) ); diff --git a/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_id_to_enabled_map.ts b/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_id_to_enabled_map.ts index cffe536549e9c..2a6a1e0b090c7 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_id_to_enabled_map.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/rules/transform_utils/get_rule_id_to_enabled_map.ts @@ -7,7 +7,7 @@ import type { SavedObjectsFindResult } from '@kbn/core/server'; // eslint-disable-next-line no-restricted-imports -import type { LegacyIRuleActionsAttributesSavedObjectAttributes } from '../../../../lib/detection_engine/rule_actions/legacy_types'; +import type { LegacyIRuleActionsAttributesSavedObjectAttributes } from '../../../../lib/detection_engine/rule_actions_legacy'; export const getRuleIdToEnabledMap = ( legacyRuleActions: Array< diff --git a/x-pack/plugins/security_solution/server/usage/get_internal_saved_objects_client.ts b/x-pack/plugins/security_solution/server/usage/get_internal_saved_objects_client.ts index 85fe139aa36c9..bb39b9c320b20 100644 --- a/x-pack/plugins/security_solution/server/usage/get_internal_saved_objects_client.ts +++ b/x-pack/plugins/security_solution/server/usage/get_internal_saved_objects_client.ts @@ -9,7 +9,7 @@ import type { CoreSetup, SavedObjectsClientContract } from '@kbn/core/server'; import { SAVED_OBJECT_TYPES } from '@kbn/cases-plugin/common/constants'; // eslint-disable-next-line no-restricted-imports -import { legacyRuleActionsSavedObjectType } from '../lib/detection_engine/rule_actions/legacy_saved_object_mappings'; +import { legacyRuleActionsSavedObjectType } from '../lib/detection_engine/rule_actions_legacy'; export async function getInternalSavedObjectsClient( core: CoreSetup diff --git a/x-pack/plugins/security_solution/server/usage/queries/legacy_get_rule_actions.ts b/x-pack/plugins/security_solution/server/usage/queries/legacy_get_rule_actions.ts index 8a3eba441d269..9b7140d734d16 100644 --- a/x-pack/plugins/security_solution/server/usage/queries/legacy_get_rule_actions.ts +++ b/x-pack/plugins/security_solution/server/usage/queries/legacy_get_rule_actions.ts @@ -12,10 +12,10 @@ import type { SavedObjectsCreatePointInTimeFinderOptions, } from '@kbn/core/server'; // eslint-disable-next-line no-restricted-imports -import type { LegacyIRuleActionsAttributesSavedObjectAttributes } from '../../lib/detection_engine/rule_actions/legacy_types'; +import type { LegacyIRuleActionsAttributesSavedObjectAttributes } from '../../lib/detection_engine/rule_actions_legacy'; // eslint-disable-next-line no-restricted-imports -import { legacyRuleActionsSavedObjectType } from '../../lib/detection_engine/rule_actions/legacy_saved_object_mappings'; +import { legacyRuleActionsSavedObjectType } from '../../lib/detection_engine/rule_actions_legacy'; export interface LegacyGetRuleActionsOptions { savedObjectsClient: SavedObjectsClientContract; diff --git a/x-pack/plugins/security_solution/server/usage/types.ts b/x-pack/plugins/security_solution/server/usage/types.ts index a6db2e3d71e91..1c97dffab5bb1 100644 --- a/x-pack/plugins/security_solution/server/usage/types.ts +++ b/x-pack/plugins/security_solution/server/usage/types.ts @@ -7,7 +7,7 @@ import type { CoreSetup, Logger } from '@kbn/core/server'; import type { SanitizedRule } from '@kbn/alerting-plugin/common'; -import type { RuleParams } from '../lib/detection_engine/schemas/rule_schemas'; +import type { RuleParams } from '../lib/detection_engine/rule_schema'; import type { SetupPlugins } from '../plugin'; export type CollectorDependencies = { diff --git a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts index cd1914df38561..e04ecd40ef04a 100644 --- a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts @@ -9,7 +9,7 @@ import { Transform } from 'stream'; import { has, isString } from 'lodash/fp'; import { createMapStream, createFilterStream } from '@kbn/utils'; -import type { ImportRulesSchema } from '../../../common/detection_engine/schemas/request/import_rules_schema'; +import type { RuleToImport } from '../../../common/detection_engine/rule_management'; export interface RulesObjectsExportResultDetails { /** number of successfully exported objects */ @@ -29,7 +29,7 @@ export const parseNdjsonStrings = (): Transform => { }; export const filterExportedCounts = (): Transform => { - return createFilterStream<ImportRulesSchema | RulesObjectsExportResultDetails>( + return createFilterStream<RuleToImport | RulesObjectsExportResultDetails>( (obj) => obj != null && !has('exported_count', obj) ); }; diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 76e347d7a0b17..f69973fdac74f 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -47,6 +47,7 @@ { "path": "../security/tsconfig.json" }, { "path": "../spaces/tsconfig.json" }, { "path": "../threat_intelligence/tsconfig.json" }, - { "path": "../timelines/tsconfig.json" } + { "path": "../timelines/tsconfig.json" }, + { "path": "../files/tsconfig.json"} ] } diff --git a/x-pack/plugins/session_view/common/constants.ts b/x-pack/plugins/session_view/common/constants.ts index e7efb0b1f11f6..9938076a6c9e1 100644 --- a/x-pack/plugins/session_view/common/constants.ts +++ b/x-pack/plugins/session_view/common/constants.ts @@ -5,6 +5,8 @@ * 2.0. */ +export const SESSION_VIEW_APP_ID = 'sessionView'; + // routes export const PROCESS_EVENTS_ROUTE = '/internal/session_view/process_events'; export const ALERTS_ROUTE = '/internal/session_view/alerts'; @@ -12,6 +14,9 @@ export const ALERT_STATUS_ROUTE = '/internal/session_view/alert_status'; export const IO_EVENTS_ROUTE = '/internal/session_view/io_events'; export const GET_TOTAL_IO_BYTES_ROUTE = '/internal/session_view/get_total_io_bytes'; +export const SECURITY_APP_ID = 'security'; +export const POLICIES_PAGE_PATH = '/administration/policy'; + // index patterns export const PROCESS_EVENTS_INDEX = '*:logs-endpoint.events.process*,logs-endpoint.events.process*'; // match on both cross cluster and local indices export const PREVIEW_ALERTS_INDEX = '.preview.alerts-security.alerts-default'; diff --git a/x-pack/plugins/session_view/common/mocks/responses/session_view_io_events.mock.ts b/x-pack/plugins/session_view/common/mocks/responses/session_view_io_events.mock.ts index 23cf1ede0d9a3..c8be02c00bbed 100644 --- a/x-pack/plugins/session_view/common/mocks/responses/session_view_io_events.mock.ts +++ b/x-pack/plugins/session_view/common/mocks/responses/session_view_io_events.mock.ts @@ -282,6 +282,7 @@ export const sessionViewIOEventsMock: ProcessEventResults = { total_bytes_captured: 1024, total_bytes_skipped: 0, bytes_skipped: [], + max_bytes_per_process_exceeded: true, text: '\u001b[38;5;130m 84 \n 85 \u001b[mif [[ $KILL_IMMEDIATELY == 1 ]]; then\n\u001b[38;5;130m 86 \u001b[m echo "WARNING: Not waiting for connections to close gracefully"\n\u001b[38;5;130m 87 \u001b[m echo "Press any key to continue... wsrep_reject_queries will be set to \'ALL_KILL\'"\n\u001b[38;5;130m 88 \u001b[m read a\n\u001b[38;5;130m 89 \u001b[m mysql -h127.0.0.1 -P3306 -uroot -e "set global wsrep_reject_queries=\'ALL_KILL\'"\n\u001b[38;5;130m 90 \u001b[melse\n\u001b[38;5;130m 91 \u001b[m # Stop accepting queries in mariadb, do not kill opened connections\n\u001b[38;5;130m 92 \u001b[m mysql -h127.0.0.1 -P3306 -uroot -e "set global wsrep_reject_queries=\'ALL\'"\n\u001b[38;5;130m 93 \u001b[mfi\n\u001b[38;5;130m 94 \n 95 \u001b[mexit_code=$?\n\u001b[38;5;130m 96 \u001b[mif [[ $exit_code != 0 ]]; then\n\u001b[38;5;130m 97 \u001b[m >&2 echo "Failed to set the reject of queries on Mysql node, exiting."\n\u001b[38;5;130m 98 \u001b[m exit $exit_code\n\u001b[38;5;130m 99 \u001b[melse\n\u001b[38;5;130m 100 \u001b[m echo "Successfully stopped accepting queries."\n\u001b[38;5;130m 101 \u001b[m if [[ $KILL_IMMEDIATELY == 1 ]]; then\n\u001b[38;5;130m 102 \u001b[m\u001b[8Cexit\n\u001b[38;5;130m 103 \u001b[m fi\n\u001b[38;5;130m 104 \u001b[mfi\n\u001b[38;5;130m 105 \n 106 \u001b[mif [[ $GRACE_PERIOD == -1 ]]; then\n\u001b[38;5;130m 107 \u001b[m set_number_grace_seconds\n\u001b[38;5;130m 108 \u001b[mfi\n\u001b[38;5;130m 109 \n 110 \u001b[mwait_for_connections\n\u001b[38;5;130m 111 \u001b[mif [[ $DB_CONNECTIONS_NUMBER != 0 ]]; then\n\u001b[38;5;130m 112 \u001b[m get_number_db_connections\n\u001b[38;5;130m 113 \u001b[m >&2 echo "ERROR: There are still $DB_CONNECTIONS_NUMBER opened DB connections."\n\u001b[38;5;130m 114 \u001b[m exit 3\n\u001b[38;5;130m 115 \u001b[mfi\b\b\u001b[?25h\u001b[?25l\nType :qa! and press <Enter> to abandon all changes and exit Vim\u0007\u001b[58;9H\u001b[?25h\u0007\u001b[?25l\u001b[59;1H\u001b[K\u001b[59;1H:\u001b[?2004h\u001b[?25hqa!\r\u001b[?25l\u001b[?2004l\u001b[59;1H\u001b[K\u001b[59;1H\u001b[?2004l\u001b[?1l\u001b>\u001b[?25h\u001b[?1049l\u001b[23;0;0t,\u001bkroot@staging-host:~\u001b\\\n', }, tty: { diff --git a/x-pack/plugins/session_view/common/types/process_tree/index.ts b/x-pack/plugins/session_view/common/types/process_tree/index.ts index 68f8924abd702..b228502f61a54 100644 --- a/x-pack/plugins/session_view/common/types/process_tree/index.ts +++ b/x-pack/plugins/session_view/common/types/process_tree/index.ts @@ -72,6 +72,7 @@ export interface IOLine { export interface ProcessStartMarker { event: ProcessEvent; line: number; + maxBytesExceeded?: boolean; } export interface IOFields { diff --git a/x-pack/plugins/session_view/public/components/session_view/index.tsx b/x-pack/plugins/session_view/public/components/session_view/index.tsx index 32cc5dffdea5d..18296992de65f 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.tsx @@ -53,6 +53,7 @@ export const SessionView = ({ jumpToCursor, investigatedAlertId, loadAlertDetails, + canAccessEndpointManagement, }: SessionViewDeps) => { // don't engage jumpTo if jumping to session leader. if (jumpToEntityId === sessionEntityId) { @@ -422,6 +423,7 @@ export const SessionView = ({ isFullscreen={isFullScreen} onJumpToEvent={onJumpToEvent} autoSeekToEntityId={currentJumpToOutputEntityId} + canAccessEndpointManagement={canAccessEndpointManagement} /> </div> ); diff --git a/x-pack/plugins/session_view/public/components/tty_player/ansi_helpers.ts b/x-pack/plugins/session_view/public/components/tty_player/ansi_helpers.ts new file mode 100644 index 0000000000000..e5ad54f7e5269 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/tty_player/ansi_helpers.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 { Teletype } from '../../../common/types/process_tree'; +import { + PROCESS_DATA_LIMIT_EXCEEDED_START, + PROCESS_DATA_LIMIT_EXCEEDED_END, + VIEW_POLICIES, +} from './translations'; + +export const renderTruncatedMsg = (tty?: Teletype, policiesUrl?: string, processName?: string) => { + if (tty?.columns) { + const lineBreak = '-'.repeat(tty.columns); + const message = ` ⚠ ${PROCESS_DATA_LIMIT_EXCEEDED_START} \x1b[1m${processName}.\x1b[22m ${PROCESS_DATA_LIMIT_EXCEEDED_END}`; + const link = policiesUrl + ? `\x1b[${Math.min( + message.length + 2, + tty.columns - VIEW_POLICIES.length - 4 + )}G\x1b[1m\x1b]8;;${policiesUrl}\x1b\\[ ${VIEW_POLICIES} ]\x1b]8;;\x1b\\\x1b[22m` + : ''; + + return `\n\x1b[33m${lineBreak}\n${message}${link}\n${lineBreak}\x1b[0m\n\n`; + } +}; diff --git a/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx b/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx index 9f7201492520c..b40605ad2cdba 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx @@ -30,7 +30,7 @@ describe('TTYPlayer/hooks', () => { })), }); - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); describe('useIOLines', () => { diff --git a/x-pack/plugins/session_view/public/components/tty_player/hooks.ts b/x-pack/plugins/session_view/public/components/tty_player/hooks.ts index b6891f1dd1d49..30b40beaa094f 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/hooks.ts +++ b/x-pack/plugins/session_view/public/components/tty_player/hooks.ts @@ -12,6 +12,7 @@ import { CoreStart } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { SearchAddon } from './xterm_search'; import { useEuiTheme } from '../../hooks'; +import { renderTruncatedMsg } from './ansi_helpers'; import { IOLine, @@ -103,6 +104,15 @@ export const useIOLines = (pages: ProcessEventsPage[] | undefined) => { newMarkers.push(processLineInfo); } + if (process.io.max_bytes_per_process_exceeded) { + const marker = newMarkers.find( + (item) => item.event.process?.entity_id === process.entity_id + ); + if (marker) { + marker.maxBytesExceeded = true; + } + } + const splitLines = process.io.text.split(TTY_LINE_SPLITTER_REGEX); const combinedLines = [splitLines[0]]; @@ -158,6 +168,7 @@ export interface XtermPlayerDeps { hasNextPage?: boolean; fetchNextPage?: () => void; isFetching?: boolean; + policiesUrl?: string; } export const useXtermPlayer = ({ @@ -169,17 +180,20 @@ export const useXtermPlayer = ({ hasNextPage, fetchNextPage, isFetching, + policiesUrl, }: XtermPlayerDeps) => { const { euiTheme } = useEuiTheme(); const { font, colors } = euiTheme; const [currentLine, setCurrentLine] = useState(0); const [playSpeed] = useState(DEFAULT_TTY_PLAYSPEED_MS); // potentially configurable const tty = lines?.[currentLine]?.event.process?.tty; - + const processName = lines?.[currentLine]?.event.process?.name; const [terminal, searchAddon] = useMemo(() => { const term = new Terminal({ theme: { - selection: colors.warning, + selectionBackground: colors.warning, + selectionForeground: colors.ink, + yellow: colors.warning, }, fontFamily: font.familyCode, fontSize: DEFAULT_TTY_FONT_SIZE, @@ -187,6 +201,8 @@ export const useXtermPlayer = ({ convertEol: true, rows: DEFAULT_TTY_ROWS, cols: DEFAULT_TTY_COLS, + allowProposedApi: true, + allowTransparency: true, }); const searchInstance = new SearchAddon(); @@ -203,7 +219,7 @@ export const useXtermPlayer = ({ // even though we set scrollback: 0 above, xterm steals the wheel events and prevents the outer container from scrolling // this handler fixes that const onScroll = (event: WheelEvent) => { - if ((event?.target as HTMLDivElement)?.className === 'xterm-cursor-layer') { + if ((event?.target as HTMLDivElement)?.offsetParent?.classList.contains('xterm-screen')) { event.stopImmediatePropagation(); } }; @@ -212,6 +228,7 @@ export const useXtermPlayer = ({ return () => { window.removeEventListener('wheel', onScroll, true); + terminal.dispose(); }; }, [terminal, ref]); @@ -241,17 +258,30 @@ export const useXtermPlayer = ({ if (line?.value !== undefined) { terminal.write(line.value); } + + const nextLine = lines[lineNumber + index + 1]; + const maxBytesExceeded = line.event.process?.io?.max_bytes_per_process_exceeded; + + // if next line is start of next event + // and process has exceeded max bytes + // render msg + if (!clear && (!nextLine || nextLine.event !== line.event) && maxBytesExceeded) { + const msg = renderTruncatedMsg(tty, policiesUrl, processName); + if (msg) { + terminal.write(msg); + } + } }); }, - [lines, terminal] + [lines, policiesUrl, processName, terminal, tty] ); useEffect(() => { - const fontChanged = terminal.getOption('fontSize') !== fontSize; + const fontChanged = terminal.options.fontSize !== fontSize; const ttyChanged = tty && (terminal.rows !== tty?.rows || terminal.cols !== tty?.columns); if (fontChanged) { - terminal.setOption('fontSize', fontSize); + terminal.options.fontSize = fontSize; } if (tty?.rows && tty?.columns && ttyChanged) { diff --git a/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx index f3332ae5bb7f8..ba56931b4a99b 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx @@ -6,10 +6,11 @@ */ import React from 'react'; -import { waitFor } from '@testing-library/react'; +import { waitFor, act } from '@testing-library/react'; import { sessionViewIOEventsMock } from '../../../common/mocks/responses/session_view_io_events.mock'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { TTYPlayerDeps, TTYPlayer } from '.'; +import userEvent from '@testing-library/user-event'; describe('TTYPlayer component', () => { beforeAll(() => { @@ -81,5 +82,38 @@ describe('TTYPlayer component', () => { await waitForApiCall(); }); + + it('renders a message warning when max_bytes exceeded', async () => { + renderResult = mockedContext.render(<TTYPlayer {...props} />); + + await waitForApiCall(); + await new Promise((r) => setTimeout(r, 10)); + + const seekToEndBtn = renderResult.getByTestId('sessionView:TTYPlayerControlsEnd'); + + act(() => { + userEvent.click(seekToEndBtn); + }); + + waitFor(() => expect(renderResult.queryAllByText('Data limit reached')).toHaveLength(1)); + expect(renderResult.queryByText('[ VIEW POLICIES ]')).toBeFalsy(); + }); + + it('renders a message warning when max_bytes exceeded with link to policies page', async () => { + renderResult = mockedContext.render( + <TTYPlayer {...props} canAccessEndpointManagement={true} /> + ); + + await waitForApiCall(); + await new Promise((r) => setTimeout(r, 10)); + + const seekToEndBtn = renderResult.getByTestId('sessionView:TTYPlayerControlsEnd'); + + act(() => { + userEvent.click(seekToEndBtn); + }); + + waitFor(() => expect(renderResult.queryAllByText('[ VIEW POLICIES ]')).toHaveLength(1)); + }); }); }); diff --git a/x-pack/plugins/session_view/public/components/tty_player/index.tsx b/x-pack/plugins/session_view/public/components/tty_player/index.tsx index c77efc9d8c152..434805ac689db 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/index.tsx @@ -13,6 +13,8 @@ import { EuiButton, EuiBetaBadge, } from '@elastic/eui'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { CoreStart } from '@kbn/core/public'; import useResizeObserver from 'use-resize-observer'; import { throttle } from 'lodash'; import { ProcessEvent } from '../../../common/types/process_tree'; @@ -23,6 +25,8 @@ import { DEFAULT_TTY_ROWS, DEFAULT_TTY_COLS, DEFAULT_TTY_FONT_SIZE, + POLICIES_PAGE_PATH, + SECURITY_APP_ID, } from '../../../common/constants'; import { useFetchIOEvents, useIOLines, useXtermPlayer } from './hooks'; import { TTYPlayerControls } from '../tty_player_controls'; @@ -35,6 +39,7 @@ export interface TTYPlayerDeps { isFullscreen: boolean; onJumpToEvent(event: ProcessEvent): void; autoSeekToEntityId?: string; + canAccessEndpointManagement?: boolean; } export const TTYPlayer = ({ @@ -44,6 +49,7 @@ export const TTYPlayer = ({ isFullscreen, onJumpToEvent, autoSeekToEntityId, + canAccessEndpointManagement, }: TTYPlayerDeps) => { const ref = useRef<HTMLDivElement>(null); const { ref: scrollRef, height: containerHeight = 1 } = useResizeObserver<HTMLDivElement>({}); @@ -53,7 +59,16 @@ export const TTYPlayer = ({ const { lines, processStartMarkers } = useIOLines(data?.pages); const [fontSize, setFontSize] = useState(DEFAULT_TTY_FONT_SIZE); const [isPlaying, setIsPlaying] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); const [currentAutoSeekEntityId, setCurrentAutoSeekEntityId] = useState(''); + const { getUrlForApp } = useKibana<CoreStart>().services.application; + const policiesUrl = useMemo( + () => + canAccessEndpointManagement + ? getUrlForApp(SECURITY_APP_ID, { path: POLICIES_PAGE_PATH }) + : '', + [canAccessEndpointManagement, getUrlForApp] + ); const { search, currentLine, seekToLine } = useXtermPlayer({ ref, @@ -64,6 +79,7 @@ export const TTYPlayer = ({ hasNextPage, fetchNextPage, isFetching, + policiesUrl, }); const currentProcessEvent = lines[Math.min(lines.length - 1, currentLine)]?.event; @@ -113,11 +129,18 @@ export const TTYPlayer = ({ const styles = useStyles(tty, show); + const clearSearch = useCallback(() => { + if (searchQuery) { + setSearchQuery(''); + } + }, [searchQuery]); + const onSeekLine = useMemo(() => { return throttle((line: number) => { + clearSearch(); seekToLine(line); }, 100); - }, [seekToLine]); + }, [clearSearch, seekToLine]); const onTogglePlayback = useCallback(() => { // if at the end, seek to beginning @@ -127,6 +150,12 @@ export const TTYPlayer = ({ setIsPlaying(!isPlaying); }, [currentLine, isPlaying, lines.length, seekToLine]); + useEffect(() => { + if (isPlaying) { + clearSearch(); + } + }, [clearSearch, isPlaying]); + return ( <div css={styles.container}> <EuiPanel hasShadow={false} borderRadius="none" hasBorder={false} css={styles.header}> @@ -140,6 +169,8 @@ export const TTYPlayer = ({ seekToLine={seekToLine} xTermSearchFn={search} setIsPlaying={setIsPlaying} + searchQuery={searchQuery} + setSearchQuery={setSearchQuery} /> </EuiFlexItem> @@ -157,11 +188,17 @@ export const TTYPlayer = ({ </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiButtonIcon iconType="refresh" display="empty" size="m" disabled={true} /> + <EuiButtonIcon + iconType="refresh" + display="empty" + size="m" + disabled={true} + aria-label="disabled" + /> </EuiFlexItem> <EuiFlexItem grow={false}> - <EuiButtonIcon iconType="eye" disabled={true} size="m" /> + <EuiButtonIcon iconType="eye" disabled={true} size="m" aria-label="disabled" /> </EuiFlexItem> <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/session_view/public/components/tty_player/translations.ts b/x-pack/plugins/session_view/public/components/tty_player/translations.ts new file mode 100644 index 0000000000000..8d1dfd7497410 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/tty_player/translations.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 { i18n } from '@kbn/i18n'; + +export const PROCESS_DATA_LIMIT_EXCEEDED_START = i18n.translate( + 'xpack.sessionView.processDataLimitExceededStart', + { + defaultMessage: 'Data limit reached for', + } +); + +export const PROCESS_DATA_LIMIT_EXCEEDED_END = i18n.translate( + 'xpack.sessionView.processDataLimitExceededEnd', + { + defaultMessage: 'See "max_kilobytes_per_process" in advanced policy configuration.', + } +); + +export const VIEW_POLICIES = i18n.translate('xpack.sessionView.viewPoliciesLink', { + defaultMessage: 'VIEW POLICIES', +}); diff --git a/x-pack/plugins/session_view/public/components/tty_player/xterm_search.ts b/x-pack/plugins/session_view/public/components/tty_player/xterm_search.ts index 3c430d691e3f7..fee2aff4b5458 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/xterm_search.ts +++ b/x-pack/plugins/session_view/public/components/tty_player/xterm_search.ts @@ -9,7 +9,7 @@ * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ -import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm'; +import { Terminal, IDisposable, ITerminalAddon, IBufferRange } from 'xterm'; export interface ISearchOptions { regex?: boolean; @@ -83,14 +83,14 @@ export class SearchAddon implements ITerminalAddon { let startCol = 0; let startRow = 0; - let currentSelection: ISelectionPosition | undefined; + let currentSelection: IBufferRange | undefined; if (this._terminal.hasSelection()) { const incremental = searchOptions ? searchOptions.incremental : false; // Start from the selection end if there is a selection // For incremental search, use existing row currentSelection = this._terminal.getSelectionPosition()!; - startRow = incremental ? currentSelection.startRow : currentSelection.endRow; - startCol = incremental ? currentSelection.startColumn : currentSelection.endColumn; + startRow = incremental ? currentSelection.start.y : currentSelection.end.y; + startCol = incremental ? currentSelection.start.x : currentSelection.end.x; } if (searchOptions?.lastLineOnly) { @@ -139,7 +139,7 @@ export class SearchAddon implements ITerminalAddon { // If there is only one result, wrap back and return selection if it exists. if (!result && currentSelection) { - searchPosition.startRow = currentSelection.startRow; + searchPosition.startRow = currentSelection.start.y; searchPosition.startCol = 0; result = this._findInLine(term, searchPosition, searchOptions); } @@ -170,12 +170,12 @@ export class SearchAddon implements ITerminalAddon { let startCol = this._terminal.cols; let result: ISearchResult | undefined; const incremental = searchOptions ? searchOptions.incremental : false; - let currentSelection: ISelectionPosition | undefined; + let currentSelection: IBufferRange | undefined; if (this._terminal.hasSelection()) { currentSelection = this._terminal.getSelectionPosition()!; // Start from selection start if there is a selection - startRow = currentSelection.startRow; - startCol = currentSelection.startColumn; + startRow = currentSelection.start.y; + startCol = currentSelection.start.x; } else if (searchOptions?.lastLineOnly) { startRow = this._terminal.buffer.active.cursorY - 1; startCol = this._terminal.cols; @@ -194,8 +194,8 @@ export class SearchAddon implements ITerminalAddon { if (!isOldResultHighlighted) { // If selection was not able to be expanded to the right, then try reverse search if (currentSelection) { - searchPosition.startRow = currentSelection.endRow; - searchPosition.startCol = currentSelection.endColumn; + searchPosition.startRow = currentSelection.end.y; + searchPosition.startCol = currentSelection.end.x; } result = this._findInLine(term, searchPosition, searchOptions, true); } diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx index 8a536e1ae0228..61a3958c1b88d 100644 --- a/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/index.test.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { ProcessEvent } from '../../../common/types/process_tree'; import { TTYPlayerControls, TTYPlayerControlsDeps } from '.'; +import { TTYPlayerLineMarkerType } from './tty_player_controls_markers'; const MOCK_PROCESS_EVENT_START: ProcessEvent = { process: { @@ -100,11 +101,11 @@ describe('TTYPlayerControls component', () => { expect(props.onSeekLine).toHaveBeenCalledWith(9); }); - it('render output markers', async () => { + it('render process_changed markers', async () => { renderResult = mockedContext.render(<TTYPlayerControls {...props} />); expect( renderResult.queryAllByRole('button', { - name: 'output', + name: TTYPlayerLineMarkerType.ProcessChanged, }) ).toHaveLength(props.processStartMarkers.length); }); @@ -115,12 +116,10 @@ describe('TTYPlayerControls component', () => { event: { process: { ...MOCK_PROCESS_EVENT_MIDDLE, - io: { - max_bytes_per_process_exceeded: true, - }, }, }, line: 2, + maxBytesExceeded: true, }, { event: MOCK_PROCESS_EVENT_END, line: 4 }, ]; @@ -129,12 +128,12 @@ describe('TTYPlayerControls component', () => { ); expect( renderResult.queryAllByRole('button', { - name: 'output', + name: TTYPlayerLineMarkerType.ProcessChanged, }) ).toHaveLength(2); expect( renderResult.queryAllByRole('button', { - name: 'data_limited', + name: TTYPlayerLineMarkerType.ProcessDataLimitReached, }) ).toHaveLength(1); }); diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/index.tsx b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/index.tsx index b10ba00e1fc2a..e84ed7fcf34a9 100644 --- a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/index.tsx @@ -19,9 +19,14 @@ type Props = { onSeekLine(line: number): void; }; +export enum TTYPlayerLineMarkerType { + ProcessChanged = 'process_changed', + ProcessDataLimitReached = 'data_limited', +} + type TTYPlayerLineMarker = { line: number; - type: 'output' | 'data_limited'; + type: TTYPlayerLineMarkerType; name: string; }; @@ -44,10 +49,11 @@ export const TTYPlayerControlsMarkers = ({ return []; } return processStartMarkers.map( - ({ event, line }) => + ({ event, line, maxBytesExceeded }) => ({ - type: - event.process?.io?.max_bytes_per_process_exceeded === true ? 'data_limited' : 'output', + type: maxBytesExceeded + ? TTYPlayerLineMarkerType.ProcessDataLimitReached + : TTYPlayerLineMarkerType.ProcessChanged, line, name: event.process?.name, } as TTYPlayerLineMarker) diff --git a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts index f48cfd3795eb0..48c7c67128c64 100644 --- a/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts +++ b/x-pack/plugins/session_view/public/components/tty_player_controls/tty_player_controls_markers/styles.ts @@ -9,7 +9,7 @@ import { useMemo } from 'react'; import { CSSObject } from '@emotion/react'; import { useEuiTheme } from '../../../hooks'; -type TTYPlayerLineMarkerType = 'output' | 'data_limited'; +import { TTYPlayerLineMarkerType } from '.'; export const useStyles = (progress: number) => { const { euiTheme, euiVars } = useEuiTheme(); @@ -30,7 +30,7 @@ export const useStyles = (progress: number) => { }; const getMarkerBackgroundColor = (type: TTYPlayerLineMarkerType, selected: boolean) => { - if (type === 'data_limited') { + if (type === TTYPlayerLineMarkerType.ProcessDataLimitReached) { return euiVars.terminalOutputMarkerWarning; } if (selected) { @@ -105,7 +105,7 @@ export const useStyles = (progress: number) => { left: progress + '%', top: 16, fill: - type === 'data_limited' + type === TTYPlayerLineMarkerType.ProcessDataLimitReached ? euiVars.terminalOutputMarkerWarning : euiVars.terminalOutputMarkerAccent, }); diff --git a/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx index 06fa17a6c151c..7b8adbc47d52d 100644 --- a/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_search_bar/index.test.tsx @@ -7,7 +7,6 @@ import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; import userEvent from '@testing-library/user-event'; -import { fireEvent } from '@testing-library/dom'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { sessionViewIOEventsMock } from '../../../common/mocks/responses/session_view_io_events.mock'; import { useIOLines } from '../tty_player/hooks'; @@ -35,6 +34,8 @@ describe('TTYSearchBar component', () => { seekToLine: jest.fn(), xTermSearchFn: jest.fn(), setIsPlaying: jest.fn(), + searchQuery: '', + setSearchQuery: jest.fn(), }; }); @@ -44,33 +45,20 @@ describe('TTYSearchBar component', () => { }); it('does a search when a user enters text and hits enter', async () => { - renderResult = mockedContext.render(<TTYSearchBar {...props} />); - - const searchInput = renderResult.queryByTestId('sessionView:searchBar')?.querySelector('input'); - if (searchInput) { - userEvent.type(searchInput, '-h'); - fireEvent.keyUp(searchInput, { key: 'Enter', code: 'Enter' }); - } + renderResult = mockedContext.render(<TTYSearchBar {...props} searchQuery="-h" />); expect(props.seekToLine).toHaveBeenCalledTimes(1); // there is a slight delay in the seek in xtermjs, so we wait 100ms before trying to highlight a result. await new Promise((r) => setTimeout(r, 100)); - expect(props.xTermSearchFn).toHaveBeenCalledTimes(2); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(1, '', 0); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(2, '-h', 6); + expect(props.xTermSearchFn).toHaveBeenCalledTimes(1); + expect(props.xTermSearchFn).toHaveBeenNthCalledWith(1, '-h', 6); expect(props.setIsPlaying).toHaveBeenCalledWith(false); }); it('calls seekToline and xTermSearchFn when currentMatch changes', async () => { - renderResult = mockedContext.render(<TTYSearchBar {...props} />); - - const searchInput = renderResult.queryByTestId('sessionView:searchBar')?.querySelector('input'); - if (searchInput) { - userEvent.type(searchInput, '-h'); - fireEvent.keyUp(searchInput, { key: 'Enter', code: 'Enter' }); - } + renderResult = mockedContext.render(<TTYSearchBar {...props} searchQuery="-h" />); await new Promise((r) => setTimeout(r, 100)); @@ -83,27 +71,23 @@ describe('TTYSearchBar component', () => { expect(props.seekToLine).toHaveBeenNthCalledWith(1, 26); expect(props.seekToLine).toHaveBeenNthCalledWith(2, 100); - expect(props.xTermSearchFn).toHaveBeenCalledTimes(3); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(1, '', 0); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(2, '-h', 6); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(3, '-h', 13); - expect(props.setIsPlaying).toHaveBeenCalledTimes(3); + expect(props.xTermSearchFn).toHaveBeenCalledTimes(2); + expect(props.xTermSearchFn).toHaveBeenNthCalledWith(1, '-h', 6); + expect(props.xTermSearchFn).toHaveBeenNthCalledWith(2, '-h', 13); + expect(props.setIsPlaying).toHaveBeenCalledTimes(2); }); it('calls xTermSearchFn with empty query when search is cleared', async () => { - renderResult = mockedContext.render(<TTYSearchBar {...props} />); - - const searchInput = renderResult.queryByTestId('sessionView:searchBar')?.querySelector('input'); - if (searchInput) { - userEvent.type(searchInput, '-h'); - fireEvent.keyUp(searchInput, { key: 'Enter', code: 'Enter' }); - } + renderResult = mockedContext.render(<TTYSearchBar {...props} searchQuery="-h" />); await new Promise((r) => setTimeout(r, 100)); userEvent.click(renderResult.getByTestId('clearSearchButton')); await new Promise((r) => setTimeout(r, 100)); - expect(props.xTermSearchFn).toHaveBeenNthCalledWith(3, '', 0); + renderResult.rerender(<TTYSearchBar {...props} />); + + expect(props.setSearchQuery).toHaveBeenNthCalledWith(1, ''); + expect(props.xTermSearchFn).toHaveBeenNthCalledWith(2, '', 0); expect(props.setIsPlaying).toHaveBeenCalledWith(false); }); }); diff --git a/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx b/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx index 18b829127ab2d..47d166167bb2a 100644 --- a/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_search_bar/index.tsx @@ -20,6 +20,8 @@ export interface TTYSearchBarDeps { seekToLine(index: number): void; xTermSearchFn(query: string, index: number): void; setIsPlaying(value: boolean): void; + searchQuery: string; + setSearchQuery(value: string): void; } const STRIP_NEWLINES_REGEX = /^(\r\n|\r|\n|\n\r)/; @@ -29,9 +31,10 @@ export const TTYSearchBar = ({ seekToLine, xTermSearchFn, setIsPlaying, + searchQuery, + setSearchQuery, }: TTYSearchBarDeps) => { const [currentMatch, setCurrentMatch] = useState<SearchResult | null>(null); - const [searchQuery, setSearchQuery] = useState(''); const jumpToMatch = useCallback( (match) => { @@ -105,7 +108,7 @@ export const TTYSearchBar = ({ setSearchQuery(query); setCurrentMatch(null); }, - [setIsPlaying] + [setIsPlaying, setSearchQuery] ); const onSetCurrentMatch = useCallback( diff --git a/x-pack/plugins/session_view/public/test/index.tsx b/x-pack/plugins/session_view/public/test/index.tsx index 244560d366ac4..6ccc8ca2b1991 100644 --- a/x-pack/plugins/session_view/public/test/index.tsx +++ b/x-pack/plugins/session_view/public/test/index.tsx @@ -17,6 +17,7 @@ import { CoreStart } from '@kbn/core/public'; import { coreMock } from '@kbn/core/public/mocks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { SECURITY_APP_ID, SESSION_VIEW_APP_ID } from '../../common/constants'; type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; @@ -46,8 +47,10 @@ const createCoreStartMock = ( // Mock the certain APP Ids returned by `application.getUrlForApp()` coreStart.application.getUrlForApp.mockImplementation((appId) => { switch (appId) { - case 'sessionView': + case SESSION_VIEW_APP_ID: return '/app/sessionView'; + case SECURITY_APP_ID: + return '/app/security'; default: return `${appId} not mocked!`; } diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index d276f0e9518a9..fa5f9d1ebb04d 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -27,6 +27,7 @@ export interface SessionViewDeps { // Callback used when alert flyout panel is closed handleOnAlertDetailsClosed: () => void ) => void; + canAccessEndpointManagement?: boolean; } export interface EuiTabProps { diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts index 2b13553d4e604..ea71e64f2258a 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts @@ -58,7 +58,7 @@ describe('<SnapshotRestoreHome />', () => { /** * TODO: investigate why we need to skip this test. * My guess is a change in the useRequest() hook and maybe a setTimout() that hasn't been - * mocked with jest.useFakeTimers(); + * mocked with jest.useFakeTimers('legacy'); * I tested locally and the loading spinner is present in the UI so skipping this test for now. */ test.skip('should display a loading while fetching the repositories', () => { diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts index 3a34926272e07..c9da67b98d622 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts @@ -37,7 +37,7 @@ describe('<RepositoryAdd />', () => { /** * TODO: investigate why we need to skip this test. * My guess is a change in the useRequest() hook and maybe a setTimout() that hasn't been - * mocked with jest.useFakeTimers(); + * mocked with jest.useFakeTimers('legacy'); * I tested locally and the loading spinner is present in the UI so skipping this test for now. */ test.skip('should indicate that the repository types are loading', () => { diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/snapshot_list.test.tsx b/x-pack/plugins/snapshot_restore/__jest__/client_integration/snapshot_list.test.tsx index bc6eae3fcd573..a94d0fdcd371d 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/snapshot_list.test.tsx +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/snapshot_list.test.tsx @@ -55,7 +55,7 @@ describe('<SnapshotList />', () => { let getSearchErrorText: SnapshotListTestBed['actions']['getSearchErrorText']; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const snapshot = fixtures.getSnapshot({ repository: REPOSITORY_NAME, snapshot: getRandomString(), diff --git a/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts b/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts index 5aaeb97ae76c4..8250a993192bb 100644 --- a/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts +++ b/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts @@ -214,7 +214,7 @@ test('maintains unavailable status if default space cannot be created', async () }); test('retries operation', async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const { repository, serviceStatus$ } = setup({ elasticsearchStatus: ServiceStatusLevels.available, diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx index 643d6c79afc7d..e6e1b18c5de8a 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx @@ -81,7 +81,7 @@ describe('ThresholdVisualization', () => { test('periodically requests visualization data', async () => { const refreshRate = 10; - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); const wrapper = mountWithIntl( <ThresholdVisualization diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/logo.tsx index 945dc955e4b20..5ce3e292690c1 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/logo.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases/jira/logo.tsx @@ -10,26 +10,39 @@ import { LogoProps } from '../../types'; const Logo = (props: LogoProps) => ( <svg + version="1.2" + baseProfile="tiny-ps" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 1588 1588" width="32" height="32" - viewBox="0 0 32 32" - fill="none" - xmlns="http://www.w3.org/2000/svg" - xmlnsXlink="http://www.w3.org/1999/xlink" {...props} > - <rect width="32" height="32" fill="url(#pattern0)" /> <defs> - <pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1"> - <use xlinkHref="#image0" transform="translate(-0.0625 -0.0625) scale(0.0028125)" /> - </pattern> - <image - id="image0" - width="400" - height="400" - xlinkHref="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAYAAACAvzbMAAAgAElEQVR4Ae29C7AtV3nf+V/d+5z7vtJ9SLq6uhhJCMlIYDE8BALZkqGUKGEiLNBFICGwwfIjDmLkIcEPJjqqMinjmXKqQlK4aqbGk3gmsU0m44mTSuKZcimOExITKiPLkvVCKNZFgJCAi6R77zln715T33r1Wt29z9nnnP3o3vu/QWev/tZa3/rWb+37ffvr1d0b4IsESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEukxAddl42k4CYydwUuevuQZHljK8qt/DfgBrgwLPvbqPrz+4ovpjH48KSaDDBBhAOrx4NH2MBLRWl30WP5ADn8mAdwK4GMAeCSAAvq0V/kxpfPaJh/AVfFENxjgyVZFAZwkwgHR26Wj42Ais6Oy1Cu9WOX4DwGUAmv5daBNIgM+/0sevP7eizoxtfCoigY4SyDpqN80mgbERuGIZb1M5/iGAy4cEDxlLgsqFCvjM3hyfuGlF98ZmABWRQEcJMIB0dOFo9ngIvGZFX5gV+HV3ymoUpbsyhV/+ZoZrR2nMNiQwzwQYQOZ5dTm3DQlIFpHluAPAmzdsWK/cXyh86sR9z8oeCV8ksLAEGEAWduk58ecyXKMUfhHA0hZpKKVw6/5DJ67eYj82J4G5IsAAMlfLycmMSuCqz+kDOsPfBHBs1D5xOw3sHWS474pP6F2xnGUSWCQCDCCLtNqcqyWgtRqs4xYF3L7BpvmmtBRwa3YBfnDThmxAAnNKgAFkTheW0xpOQO73yIAHAOw0ezigMnzy+IreO3w01pDA/BJgAJnfteXMGgjIKae8KO4FxpM5aOD2vcu8IqsBNUULQIABZAEWmVMsCWQX4dosy+7ZyamrUpspHUCBj8meSkXOQxKYewIMIHO/xJygJ+Cc/KcAjNXZZ8Adep1ZiOfM98UhwACyOGu98DNVfbwDwK0TALG/AD7KLGQCZKmy1QQYQFq9PDRuXASuWtHHC43PjGHjvMkkxSykCQtl806AAWTeV5jzA7RWWMItAN42QRz7FbgXMkG+VN1CAgwgLVwUmjReApc/gNdqjV/exh3nWzFEHrbIvZCtEGPbzhNgAOn8EnICGxFwz7u6DcClG7UbR52W3w/RuId7IeOgSR1dIMAA0oVVoo3bJiDPu8oUPglgGp91eUbW7bwia9vLxY4dIzCNf1QdQ0Jz54WA3CGuFT603eddbYeDZCG8Ims75NiniwQYQLq4arR5JAL7e3i9UvjJMd40OMq4Kgfu1Ot43SiN2YYEukyAAaTLq0fbhxKQ7KMoCvmtj8NDG02oQrIQpXhF1oTwUm2LCDCAtGgxaMr4CEj2kWXZx6ecffgJKK1NFsJfLfRE+D6XBBhA5nJZF3tSUfZxcIYk9gP4EK/ImuEKcOiJE2AAmThiDjBtAgczvC7Lso/NKPvw05X7Qu4aDPAmL+A7CcwbAQaQeVvRBZ/P1St6ucjM867OawGKg1mBDzALacFK0ISJEGAAmQhWKp0VgTWsXQFgnI9r38lUmIXshB77tp4AA0jrl4gGjkzgpM5VvvxuABeN3GfyDU0Wcvmv6jZkRJOfLUdYKAIMIAu13PM92cuvwWuUws9O6a7zUWFKFnK3WuVeyKjA2K47BBhAurNWtHQjAis6W1rCDQDkFFbbXvtVjjsvXdHnt80w2kMCOyHAALITeuzbGgKX9fAqrc3ex1JrjCoNMb8XsryEN5tHy5dylkig0wQYQDq9fDTeENBaLQNvVMAPtZiI/F7IyUsfAPdCWrxING1rBBhAtsaLrVtI4PLPQW4YfK8G9rbQPG+SuTu9t8zfTvdA+N59Agwg3V/DxZ6B1iorcLUG3tMBEAfkvhBekdWBlaKJIxFgABkJExu1lcBNDyBXBeTS3aNttbFi111ZgTdWZDwkgU4SYADp5LLRaE/gL4DLFfATLbt015vX9H4w03g/r8hqQkNZ1wgwgHRtxWhvSWBFZ3mGtwB4VSlsfUlB4+5c4b9pvaU0kAQ2IcAAsgkgVreXwKXAQWT4qwDaeOnuRuDOy3Pczr2QjRCxrgsEGEC6sEq0sZFAbxmvU8AtjZXtFsrd6Xf0BngL7wtp90LRuo0JMIBszIe1bSVwUueZxjsAHGqriZvYdVjuC3GXIG/SlNUk0E4CDCDtXBdatQmBq67BRdDmse1d/QzLfSG3MwvZZKFZ3WoCXf3H12qoNG7CBLRWesnckNf1jehDvDt9wp8Vqp8oAQaQieKl8kkQuPoBLEHj7QD2TUL/FHVmWuOO3Ut4G/dCpkidQ42NAAPI2FBS0bQInO3h4kKbq6/m4fN7sK9xO5+RNa1PD8cZJ4F5+Ac4Th7U1XYC7sGJmcJVbTd1RPuyDLidWciItNisVQQYQFq1HDRmMwJX3IvlQuP6OTh9FU/1vELjVmYhMRKWu0CAAaQLq0QbA4HBRTimYZ59NU+fXbkv5E7+XkhYZhY6QmCe/hF2BDnN3DYBrVVP4w2Zwmu3raO9Hc0j6XlfSHsXiJbVCTCA1JlQ0lICcvpKKbwV7f7dj+3Sy6BxZ76K63hF1nYRst+0CTCATJs4x9s2gfwIjgD4SwB621bS7o6Hsh7ezyyk3YtE60oCDCAlC5baTGBFZ4NdeD2AywHInsE8vuS+kNt4d/o8Lu18zokBZD7Xde5mdROQZQXe0OFnX426Jkf5jKxRUbHdrAkwgMx6BTj+SASeAfZr4Efm+PSV52CyELMXsqL579NT4XsrCfAD2sploVEJAbl5MMOrlTansOb19FU85aM6xwfM753EUpZJoGUEGEBatiA0p4HAB5CpHG8GcEFD7TyK5O70W5eXcD2Yhczj+s7NnBhA5mYp53ciVxxDryhw5Zzdfb7Zgh1VGieZhWyGifWzJMAAMkv6HHskAvoIzlMKN87x1VdNHDIN3LqU4wZmIU14KGsDAQaQNqwCbRhOQGullvG6Ob98d9j8D2mF916+GweGNaCcBGZJgAFklvQ59qYEbnoAea7xGgXs37Tx/DWQvRC5L+QdvDt9/hZ3HmbEADIPqzjHczj1InLZ/9DA7jme5kZTk18tfO9Vv7aQAXQjLqxrAQEGkBYsAk0YTqB3Mfa7518t6mdV7gu5fXCuzyxk+MeENTMisKj/KGeEm8NulUBR4DVQ5gqsrXadp/YH817vNmYh87Sk8zEXBpD5WMf5nMVJnesCV0DjvPmc4MizWpJnZBXn8HZekTUyMzacAgEGkClA5hDbIyD3fyAzD09c1P2PGNyhLMeHeEVWjITlWRNgAJn1CnD8oQTyE1jWwKsX4PlXQxlEFUsaeI95Ui/vTo+wsDhLAgwgs6TPsTcmcBYHlDY/ILUIz7/amIWtPaQ07mIWMgoqtpkGAQaQaVDmGNsiMFjGa5XChdvqPJ+dJAu5hVnIfC5uF2fFANLFVVsEm09qfwPhom+gV1dbnpF111V7sK9awWMSmDYBBpBpE+d4IxEwD1DUOK6BpZE6LE4jsxdSrOFGXpG1OIve1pkygLR1ZRbcLtlAz+zP1+YLjqJp+uZXC5mFNKGhbJoEGECmSZtjjUygfxZ7CuAyAPyM1qllULjFZCFa8wKDOh9KpkSA/zinBJrDbIGA1irPcLECfmALvRat6dFM4X3HH8CeRZs459seAgwg7VkLWuIJPAClMlwKLPwd6J5I07v8Xsh79i/hRj6ptwkPZdMgwAAyDcocY2sEHoEqgIuxmI9w3wqro4VmFrIVYGw7XgIMIOPlSW1jICBXYCkbQPj53Jin/F4Is5CNGbF2ggT4D3SCcKl6ewTkCqwCOL693gvX6yL57XQ+qXfh1r0VE2YAacUy0IiYgBqYS3j5DKwYyvCy7IXcMihwPe8LGQ6JNZMhwAAyGa7UugMCeg27AJwAwEtUR+N4Udbn3emjoWKrcRJgABknTeraOQGtle7hBBQO7FzZwmiQ+0Ju1n1cxyuyFmbNWzFRBpBWLAONCATkEl7ZQNe8vyEwGa1wEQp8hHsho8Fiq/EQYAAZD0dqGReBR6CgcATA8rhULoieDBnepfu4gVnIgqx4C6bJANKCRaAJJYGbroHSwCEA/BXCEstoJXn4pMZdvDt9NFxstXMCDCA7Z0gNYyRw6kXk0Dg4RpWLpCpTwLsO9HAzs5BFWvbZzZUBZHbsOXIDAbkHRClcBIBP4W3gM4LoogI4eeLnTzGDGwEWm+yMAAPIzvix95gJyD0gGrhgzGoXSV2mFG7cc/jEu5mFLNKyz2auDCCz4c5RhxBYX0OuNQ4PqaZ4FAIax6HwwSvu5YUIo+Bim+0TYADZPjv2nACBHtCDNpvovIlw+3xNFpIdw7u2r4I9SWBzAgwgmzNiiykSWAf2K3sTIQPITri7K7Ku+ISWu/r5IoGJEGAAmQhWKt0ugeUMB6B46mW7/KJ+JgvB0f5N3AuJqLA4VgIMIGPFSWU7IqC1WpdLeDWvwNoRR99Z47jKe3fziiwPhO/jJsAAMm6i1Ld9Ag9AZRn28S707SOs9LRXZJ1/gnenV8DwcDwEGEDGw5FaxkHgESitzDOweA/IOHiKDo1LVIaPMQsZF1DqiQkwgMQ0WJ45gZwb6ONeA4UMN+w/euJHxq2Y+kiAAYSfgdYQkOdgFdpsoPNzOc5V0bik0Pg4r8gaJ1TqEgL8h8rPQbsIaCwpPsZk3GsiTzi+Xl2Im8etmPoWmwADyGKvf+tmrzLzKBPugYx7ZexeyE8wCxk32MXWxwCy2OvfutkXBXqtM2o+DJIbM6/j3enzsZhtmQUDSFtWgnaQwKQJaFwC4CdvWtEM0pNmvSD6GUAWZKG7Mk2lsNQVWztop4LG9ad28RlZHVy7VprMANLKZVlMo54vL+rg53JyH4Fj2YBZyOTwLpZm/kNdrPVu/WwVmIFMeJFkL+SGryv86ITHofoFIMAAsgCLzCmSQIXAMZXjHu6FVKjwcMsEGEC2jIwdJklAA+uT1E/dhoDJQp5bwo3kQQI7IcAAshN67DtWAhcChdYmgBRjVUxlTQSOQeOneV9IExrKRiXAADIqKbYjgfkiYLKQ7BhumK9pcTbTJMAAMk3aHGtTAlmG/qaN2GBcBI4B+OvMQsaFc/H0MIAs3pq3fsYKGLTeyPkw0NwXguPcC5mP5Zz+LBhAps+cI25AQBdY26CaVeMncEwV+LmrV/Ty+FVT47wTYACZ9xXu2PyyDK9oZiDTXDWlNN6+rpiFTBP6vIzFADIvKzkH83jwEehigHNgAJn2al6AHv4G7wuZNvbuj8cA0v01nJ8ZXAMtGQgDyNSX1GQhzyr88NRH5oCdJsAA0unlmz/jBznOMoDMZF0vyHr475iFzIR9ZwdlAOns0s2h4fdDZwOcYQCZydqaLOS5Ht4+k9E5aCcJMIB0ctnm1GhxYQVe0lrzcSazWeILtcLfZBYyG/hdHJUBpIurNsc2r2f4nlJK9kH4mgEBuSKLWcgMwHd0SAaQji7cvJqd9c0eyIsA9LzOseXzugDAvbwvpOWr1BLzGEBashA0wxJYWsZAK3yHAWRmnwh5RtaN/R6um5kFHLgzBBhAOrNUi2GozrGmCnxvMWbb2lmaLITPyGrt+rTGMAaQ1iwFDRECL53FABlO80qsmX4eTBaSXYS3ztQKDt56AgwgrV+ixTLwSmBNazy/WLNu5WwvgMJ9zEJauTatMYoBpDVLQUOEgDzOBMALir9MOOsPhDyp90ZczPtCZr0QbR6fAaTNq7OgtmngRQ0+lbcFy39Yafw8r8hqwUq01IReS+2iWTMnoBVOIju8/NS+s2uvHBBzDl1z7XefewSr+KKa3O91XAONDM+jMJfznj9zDIttgNza+c61ZbwZwJcWGwVn30RANsv4IoGEgHzjfObpp645d+7szVoXPwqtD0kDlamvFYP+H+3etfxv3n7lG/7iwRU1/l8P1Fpd/gBe2+vhXwG4PDGMB7MgoLXC71+yjvdPZL1nMSOOOTYCDCBjQzknij70+NFssPoxXQw+Ca0vApBXZibZx1d37d39K0ePHvinp/7uq+Thh2N9vWZFX5j38H8DPP8+VrDbVyaXVf/lJz6j/mT7KthzHglwD2QeV3Wbczp815MHs8HZz+tB/+9A6+MNwUM0S0C5cvXMud889Rff+vET9z27Z5vDDe02kP0PjWcAFEMbsWKaBM7XCr/MZ2RNE3k3xmIA6cY6TdxKcQ6vZIMH9KA4OSRwVG3Ilcr/xxdeeOn2cW+y9k+fWtXAf+W9IFXkszuWvZBvZrh2dhZw5DYSYABp46rMwKb/8OSjVw7W1u5SmcpVpmS/Y3MrtN63enb1C0899mfvGWcQueK8E+sqw7eZgWy+BFNscaTI8UvjXOcp2s6hJkSAAWRCYLukVrKP/vr6/1AUA3mERXiNGkTW+/2///TTf/4j4zrF8SBQ6AG+AV7KG9aiFQWNG90VWa0wh0bMngADyOzXYOYWSPaRqezdSinIf/FrpGxE6+Orr5z7B3/y9FNvwIre+Wfqfmjdw9cBvBzbwvLMCRzJBriXd6fPfB1aY8DO/7G3Zio0ZHsEtNKF/ltaFyH7qAYR0TtCNnLl2TMvf2H3Ew9fCug0Cm3DsHwNT/KRJtsAN+kuCjfjYrxp0sNQfzcIMIB0Y50mZuXyXY++ThfFX0U189hONqL1W9b6xWfxoSeO7MhgpfT6bryiFJ7gY913RHISnQ8zC5kE1m7qZADp5rqNxWrZs1DI7tXQNvuQILKzQJLLVVzZ4JWf3enlvWvfOiUPVXwawPhvVhwLvYVVoqBwC7OQhV3/ZOIMIAmOxTowV171199XO2U1JJBU6QzZH8l1oT79rW+c/vBOrtiRK7EAfJ0PVaxSb8XxeUrjF7gX0oq1mKkRDCAzxT+7wcW5S/YBBZN91IKImFYJJNLG/xdbXtsf0Xpfv7++Ildm4aSu3skedx1afvB+DDKFv+BDFYcimmWFPCPrR7OLeF/ILBehDWMzgLRhFWZgw1NPPnrFoL/+vnjopuBg6iuntURWDTi1bESuzDqz+it78NCxbW2qK6UH/bUnAfPztrGZLLeDwH65O30nWWY7pkErdkKAAWQn9Dra12cfKlMXNAWNJlk1G5GpN7XzgcRkJVq/pd/rffbwXU+Zp/luFVcfy89phT/baj+2nwoBk4X0M7xuKqNxkFYSYABp5bJM1qhh2Ud11GqWYeqHZCNNbeWu9v76+u3fOXf6ru18U+29iLMo8Oe8I726Mq05PoAcf3tcN5C2ZlY0ZGQCDCAjo5qPhj77yPLMZB/xrBoziobLeZuyEdHTGEQU9imVf8bsh2zxJsMTRzCAwjMKOBfbyXKLCGjc/FyGa1pkEU2ZIgEGkCnCbsNQ1ezDBA2k9/3tJJA094XZD8HjTxzeyn6IbKQrhf+qYX5cqg34aEOdwAFk3AupY1kMCQPIYqyzmaVkH3LXedbLL0AcNMztH3J5/wQDidZv6Q1W7z1x36ndIyNXSqt1PATg+ZH7sOEsCNxcZHjDLAbmmLMlwAAyW/5THV2yj95S76+ZQSVWVPczXCCpGtWcVdSfm1XTF53Wkv0QKPzM89/43i1bubT3JeB7BfDveUd6dVVadXz+IMN9vC+kVWsyFWMYQKaCuQWDnNS5ZB9FURxOrDFBpCHzqGQj0kcCSfVVk0mbSjsfgOR5W+v9/qfRe+LQqKeyrgTWMoXHeEd6lXy7jhXwHt6d3q41mYY1DCDToNyCMXYvPfxqn314hx7MCtlIFCAmcFpLxst7+ZvkVNbVK1gK429QkEe7K42nALyyQTNWzZ7A+dkAP7PTR9jMfhq0YCsEGEC2QqurbSX7yJc/Uc0+thJIqlOvZR4uQ6nJKxmJLoqlQTH4uPwI1UinslZUsQb8f4D5fZCqGTxuEwGF9+49coJ7IW1akwnbwgAyYcBtUL+8/OhVmcJHagHDGVd3+vX9kaa+TTJRWdNnhQFFlqnj64PBT416Kmt5Cd+Bxpe5DxIQtrVwEBr3HV/Re9tqIO0aLwEGkPHybJ02c99Hlt+jtQ57H02Ov0lm9zKi01pDsoymvk0yo89nJFr/cDZY/dgop7IuPouzGvjP3Adp3cerapBcyPee/T28vlrB4/kkwAAyn+saZiVXXoXso7Ix3uTka7Km/REfSLajz1mW5dk+Peh//Jmnn7pmsw11sw+S4VEA3w0TY6GtBPYD+BT3Qtq6POO1iwFkvDxbpc1nHwBs9iEb4xWnLwZL0Ki+arKmQLLBRnuTvqrOfKl32WDQ/+lN7w1ZUYVaN480kd8H0VXdPG4VAfmk3LL/0ImrW2UVjZkIAQaQiWBth1KffYz3pkGZWyXgNASmWibjkMRBRGu9JE8EHuXekMGLeFFr/FsGkHZ8tjaxYn+h8CneF7IJpTmoZgCZg0VsmoKcQlBZfo9S6rBNMEwKUTbdIHuInbx0qAWDLWYjw/SJXH4NUTbUDy8/ta80rl566gjWofGnvJy3zqaFErMXkl2AH2yhbTRpjAQYQMYIs02qnn/++5fJ3ofPFvzetT8OtrrsoXpqqxY0JhBIxAYTXLT+4ZcGq3dseFnv/dC6WJPLeb8dbGehzQQOQOHnmYW0eYl2bhsDyM4Ztk6DyT6Q3auy7LA921SecrKBxGcjTu4Oq0FEJlbNHhplpr/X6XC4DKcKp0lf3svlFww/bi/rrfbw+pTO9yw/WwB/yMe7D2HUNrHCbcxC2rYo47WHAWS8PFuh7TsvnvvBvJd/OBjT4OAbM5KdnNaSwcI4YeT66a+mTMbdoW4v69XLZe+09Ph/xplc4Ut8vHvKpcVHB1SGT/K+kBav0A5NYwDZIcC2dZcrr+TKJpWpfZJRJFlFcPBSsK80I/FCt+9R2Swf6bSWqBClW++71OvlH3z6iYdPDL2s93dR6AG+ooFvOkv51nICGnjf3mX+dnrLl2nb5jGAbBtdOzuaK69yddJY5/x4YyCpOXjpUQYW37/Wd0gGUQsuZuyKPt+3Mo7vWxSD16/1+3cNvblQKd1fxjMa+BKvxmrn56/BqvNQ4GPMQhrIzIGIAWQOFtFPQbKP3bv3fDzPe4frmYeEh8ihBwdfyoZlI9It6esGbNrPqMkashGjz8i95fZdKbXU6+26baMs5Omv4GUAfwxgLe3No7YSyIA7mIW0dXV2ZhcDyM74taq3ZB9Q2UdMnGi6umqIrHrKaVggMZlCHIR8RlEJBj6jCHAagpXUNemTLKTfx48PzUK+qAZ5gS9D8WqswLf9hf3MQtq/SNuxkAFkO9Ra2Ecul5TsoygGR8OpKOO4bfYQMogmmczHJCKu0s3PxoVU5rOHoC+0rf/A1EiBpLJxL1lI1sv/WxMMh3DWu/CEAv4lT2MNAdQ+sZIsZH8Pb2qfabRoJwQYQHZCr0V9nzv91NWSfdjsQQwzEcFa6GJA4vQjWZCHWBEK4fmHiT6nPvSLONSChs804izFqC/HMN19IFEKkoUU64MPyim5SHUoPn4WrxQD/BFPYwUkXSjsh8Y9V31OH+iCsbRxNAIMIKNxanUrue+jt5R/WOviqDfUBpIhTnrE4BIHjVSf6LUxquk0lKmKA4ZtXb+nxKip2AggyzKThdi9ENc5fltRxaAwT+f9eixmudUE5KNy+2DAJ/W2epW2aBwDyBaBtbG53PcR9j5McHAO3nz7F4srTtp/268EEskokqzCdEv72kDidToaTfqaMo8mWVAvheiV4eq1Pm4fdnf62ulTzxZF8Xs8jRUxa3lRA3vyAvfwiqyWL9QWzGMA2QKsNjaV7GPXvj23A8VR44KDHw6euXIaKjQwcaUeMEQctZFJm8NSnxE1yKRdra8LGlV2tVNdRn00RqF3LS8v3Trs7vRT551YRZb9R57GqpJt9bEssOyF8PdCWr1MoxvHADI6q1a2lOxjsN7/Kevlrfs2Z4+CL5aCfZVnlUqZcfpK+m0uKwNJqc/q3KRvU+YRbIr6ikwOnaEFircs6fXbGm8slNNYwJ9ojcecKr51gIAG9irF+0I6sFQjmcgAMhKmdjbyex9Q+qh1vGKn8cDpYXkUshHJAGxbNzeXPYwWSNK+oqpRXzUwmdgwwtVaZhoKKLBrUPTfdXwFe5pW4GsP4ZTK8Ft8NlYTnfbKtMadBzO8rr0W0rJRCTCAjEqqhe3OrurXmyuvTP4gTj3Ej1Awrj74+1CwTU2XSOaKNh8xlUFnk8xmCmW7sQcSBezaveeGFx77s79sDan8/aIaFAr/AcALlRoetpvA/iLDx3hFVrsXadF9wZsAACAASURBVBTrGEBGodTCNrIRub7a/6DKYLMPEzKsmzfmhrhgC+ZvIrOTsk5fyqEyFJOg4aoTme8W9xVN0rYik0Oz72HkdmzTSrIU2yEITTsnGwz6J9TS0o8Mu6S3v4Y/d/eEhP4stJ6AfELu1ut8RlbrV2oTAxlANgHU1urV55+9Jl9e/mg4DZX4f+PBremhaAvmr/wxDjpUhlNbQZ/0Nu3krWy3kSzuK+ptDIj6Op2JPgc4DhpOFAKLHgxuHHZJ7zPA9wfAvwKw6vvxvRME5LfTP8QspBNrNdRIBpChaNpbIXsfkn3oYnDEumfn6cVkV0wyhVBtC8mh7+CmWzp9aeWFNogkjn/InklzYAojWoVDshGpbMpG4C/pXdH1z6vbTC80Hvbm8r0TBORDcedgwLvTO7FaQ4ys/4Mc0pDi9hAwv/dhsg8bMMpv+5GNzmfXAolpYivlbxk/zJGtbcoehukbFkhKxUFnnKF4M0zmYdoaiWubntZSULt2791z3eEnn5JvrbXX1/p4VgH/CMB6rZKCNhM4LyvwAWYhbV6ijW1jANmYT+tq/X0fWhdHIu9v7CyDRRkMvG8O2YMLBFZupaZ1+OMb2FNQNiMwlZaFqw76RLqBLFSa7MJnGOUYzvD0NJkdKclGdFG87Uy+ft2wS3qVxh9B4VuuK9+6QUA+CHcxC+nGYjVZyQDSRKXFMnPXOfDTxkT55xcXvCN3WUH4xu/l8dVa0s/0t5Xmr2tXyp32qJ2V2L617KHp1JTXaZVaa4dkOF5fHJyMTCn5QfQTWmfvGfaU3uLbeAwa/ysv6Q0r1JXCwVzjQ5f/qj6vKwbTzpIAA0jJovUln30A+khwsomDNp7eziMUQ4OQDEjfYf1Da9M/HIVN9qaMJNEnow8NJKU+08y1C4HO9TX9o4BjJwQord/6tUceusAfx+9PfV7JJvr/A+C7sZzl1hNQcl+IWuVeSOtXqsFABpAGKG0Vmfs+ouwjBAEx2Dj8pGCdfphM5LxdMXH8odoWzN+KzKuqbbS7dok+Z0qTbLsb7Vmev1HvXn5X42ksAP0eHi6A/8vbyffOENjf6+GOS1f0+Z2xmIYaAgwgHfkgSPZR6OzHFHAkmCzf4DfIJkw70yb0SAvi+E3scQV74IKRlZm/oToUQkYSRS5bdDZZzS6wVWV+nDLqmea1wOTayRzlVRSDfbpQ7xx2GuvpX1CnM4XfVsAZ04F/ukLAZCE9/nZ6V9Yr2MkAElC0uxCyj6ozdj7dO1kzi+DnQ2WUjYTKZMJJIJIa00z+WM3GuUcy33kzp+/bib7ExmSM0MomJ+YwsjPqq3Vx3bB7QqTbYB0Pa4V/XWpkqSME9ssVWdwL6chqOTMZQDqwXnLXuck+lArZR+LwrZ8fLRsJztg5aNfXY0icfPDhtmD+VmTSzwYRU/JqTADym+JB6MaujVFGJ9NUDsvA5HobmUJvaenKQZG9e9hprK/ej28rzUt6A/PuFOSTdXevjzd0x2RaygDSgc/A4Pvf+SEAPx2cqvxTc468FkhCzuAmFtqGgnPuUp/KYp0BS2giBdcjkTl54vStzHeo2TgskMT2yFguaITJyo9/6GIfFN554r5Tu4ONcUEpjT6+DODfxWKWO0HgABTvC+nESjkjGUBavlqH73ry4KAo3p9lZfYRHGpw5CKx//M+2B/Lu3kFnx4KzkFLbSkL/d2TdNP+0s7+Z3r4w6j/sOxBmnibgj1DZH4Ma3g1GzE3Gf7Qi6dePOzrq++P349vFMBv8MbCKpkOHGvczftCOrBOzkQGkJav1a7jR64drK7+hHfyqYM2bjzxt4nDD9Wh4NR4z2/lRqePHBvxSLrZkYyG8Mc3aM4e/BDBRhnLdWmShcokG4E5jWWvxhpirFI67+PfA/jSkBYUt5fAeZnG+3l3ensXKLaMASSm0bKyZB/r64Nb8zxzex9VBy0GlzJfFGccHHLkoKsyO11pYNVIINlUn21tm7kBzRBuHCOyf6zaqs4wVmTjBrJmfdpdjaWXvTnVd5eF/H1mIVUyrT9W0HxGVutXyRnIANLilVq66PDVPvswzt04YzHYe2t/eieVef9tXbTrFN4ixx3UhELltFboZIb0+uTdvEI3W0gOKzbWgpMz2eu0Cn0gi2x07eI5G5HC6x99/ImDoV+1UGYh/6laxePWEzjMZ2S1fo2MgQwgLV0nufJq7ezZv5b38pB9GActf8LLHphTUEHuCvIWiqFQl4kuVx0VbCAx44TKuDpYEJqYZrZtKIaCbV7aadvFfUNQ8prd/og/LOORGyPLXr17afDGUN9QkCxEF/gCs5AGOO0WySLf0T+Ht0Dr6MPSbqMX0ToGkJauuvzex9KuXR83jlX+CZl/Rraw5WxkyGZ18u2/MobBIv2icb1MbEn6eoZR20Rd6f2tCqM3tLC9m3S6dklwMd0Usjw/1uvtusEP3fiulM4KPMi9kEY6bRceznPcfvnnMDzLbPsMFsA+BpAWLrK/70MXhXnuU3CgxkGLwc59Jz7YHpTf8m27kDa4vonjd/2bZLaf7eR1WjuCItPE97V1LtCZJla5+WuLcaWhPnJwagguWhe9oiheL3fob7SE3AvZiE6r6+RTc0dvwCykzavEANLC1ZH7PiT7AHRmMw8bMoypwRnLkXPpFZmRm2/vfnKuQdTOO/6gM2hzfULbULABQw7jjCKptlqDzqitaRbahsJIey6xvtjuLM+u2uhyXttPad3Hv+V9IYZG1/4cVsDJSx8An9Tb0pVjAGnZwkj2Ifd92OzDeGDrr52DDw40+GBbMH+DTCYlB/5yWj9JK0v9v3P6rq/XL+9WQaVvotO1kSauv7x7HfX+Tqtv68fYRGdNnxuj6A8uG+T5dd7CYe/m7nTg73EvZBih1srlGVl3LCm8lXsh7VwjBpCWrYtkHxlwt1Jwa+O8bXC61uBmB2+l5tSQtA9ePQ4kkaKkaDrEXVx+E6uJOninH40RUAZVoWD1mgbORm9epb89XZaO45uEObu+Kst27d637wY0/dRtMMYYqvt9c08I7wuJuXSjfFDl+DFmIe1cLAaQFq1LNfvwew/WROdUI98qDtU41UjmI4CIjOM1ncsGdt/BV5pWoV3QF1U3yfwYXrXVWY7h5TL+Rv1Nj9AtFGx3OfSRw0jsYaJPHuE+GLz6xOlTu3yTYe8uC/l1ZiHDCLVWLj7qzuUlvLm1Fi6wYQwgrVl8rcrsQ0XZR5w9iLHGs4Y3K4lkrmhdt7uKKpKZ9rX9kdRXi5MOr6C6LouNMMHOdIramcFKx5/oNM3kjxvNFhNDygAa6XTtbC/dk32QF7713YuD7mEFpXwW8pVhTShvLQHZA3kvn9TbvvVhAGnJmlz1OeyXvQ8lT9wNzlSMKw9q2YOvavqmb+ZlG5i/8ieSSTF10K6B7dKYOSTf/l272D4x1QaSUOnGtNNI+hsD3PRcEDEWhK7myFqcVlidZiyFTOXH0OtdPuzpvKUBgGQhBcAsJIbSjbK5O52/Wti+xWIAacWaaPX9F777Rtn7gNa94GiDMxUj7UHp9EuZmYLzt5v1NWpCB9upFpgi1UHfJjJvn1dtdfoJBONMs0Sn1+vnV8aUMGfTJGRNXqcZCYNB/3xzP8hKOTNb0/BXhSuymIU04Gm56FDew23MQtq1SgwgLViPw3c9dUCeeRWyD2dTcLSJz7QHjYEkajesr5FH7bzjb9Qndkhb82Y1uoN6IAg6QyFkOEa3CxCj9jdagqpQCDq93aJP7ge5GugZ3Zv8YRayCaD2VmfyjCzeF9KuBWIAacF6yBN3zZVXgMk+gq8Vv+mctytYQTiQR5u7fY5IFvpX+zp94nyNSy79sosUo+mzvV2XeAyxIegMBWtzsDOSu6INTXJQ72+ahC6hEOatlOrlvd7FGz4Xy2p2+pmFxDg6Vj4Ejffx7vT2rBoDyIzXQh5bXc0+ag7VOWnjuEsf6iwXQRxEzJGts1X1/Qzbw2l1fV1bGwHcXkZFVgYHH4JKh5/YHJng9VmDnN4wvpNGdvp2ZqyonWkif0LBtpQA2l9b+4G9S9mrQt9NCpKF6AKfB7C6SVNWt4uA+KvbmYW0Z1EYQGa8Ft/71neusfd9qJ7xjs6ZilkmYNhC8MPBUQdnWjYYJRupByGryPwNY5fKRad9lTLv3IMt3gRnc91u39cp20k2EsbyOiUoqUP9tXOXjrKRbuailC4K/L8A/oudG/92iMBRuTudWUg7VowBZIbrIPd9KKVuy/P8aGmGeFf/TdsGkcQhu4aJzPllH2VKpy+NS6ddFq3rj8fx7UzfBn2lTmece7Mj1PVZSTR2mGBQHk5D2bGjtgaB0+mnYKrlj8jjrElBbihc3rv32ptWkIdhNikwC9kEUHurM61xG7OQdiwQA8gM10Hu+8jz/G4AuTjo8Thp52SdPqvTysxUpehe1hW7GGPkrm/FQcfBJbUx7utd+yayaJxEr7EpMc5UBxul3prnCq63AvI86/WWly59Hv7ufT/DDd7l90J6+DeA+f30DRqyqoUEjhYaH+Td6bNfGQaQGa2Bzz6KweBCa4Jz3jsJJMHBisbyIHX65ut9qLbf86Vt8Muhr9HgqkIHqQ02lmP44Wr6zHBWagcJ6t1hObbR6xX5xkP727G9eYP+4LXffPKp3b7bKO+P/SK+o4D/iXsho9BqVZssA25bXsL1mz7GplVmz58xDCCzWFNt7zqX7EMplRufGexwjjE4aamwMv9mJc4pu6phMt+3dPqRPl80I9T1xX1LG8sBa4HJ66s6/WC+Dy8iKKflx/Eyq7ccx8/b97adXX9XqXVx8Iw+G50KDK2GF5TSq/ZJvdwLGU6prTWHCo33XQr+XsgsF4gBZAb0jz+APbL3AQ3j8IxjtF+/I2usky2dtFRFjte1DE7VVdlWVuodr21a6it1RvpCsbmvtbE0wTt9Y3YYWwpBkRk22GcNi6rdOF7uWvsGpd6KziHBqbwj3Sga+c8z9+O01vgsn5E1MrK2NCyzEP5q4czWhAFk2uij7EOjWPL+VswQl1p3+pvfm9Hc1+mzlW6WpTNOg0jq9IM+39dVe42mbyQzzWoZk4slQXXokMzRzLlhHBHJywQSW7ICI7TF0FfaZeogNDZ/JlapxSlRen2APy40HqpW8bj1BA4p4L1X/Rr2t97SOTWQAWTKCyvZx9Ly8gcl+7DfwV3QcHY0ybzHLZ2+NBaHnAacpr6JLPhw29c454rMq7Uj2N7GtEo7c1iRmT4NMqPTWBvpS6YQyaV/UO6UueAkrVxlNP2yb29p12twUo98JZaZF4BnVtT3coVfYRbiiXTmXa7Iun1Q4Hr+Xshs1owBZJrctVb6le++WRfFB6D0kh/aOMbIN4q8SWakymYkvm/kSYMo6Rt8cCiEdr7vKIHEdEpstI67qW+TLPX9rm8ZWZx6K0/HigYNWU4q82rkSqyrrxn9Ut4IBGQvRGs8GstY7gSBQ9mAWcisVooBZIrkJdXO86X3272PDbKH4OudQ438pTW3elpLpM36RIN3sN5tm+NEpx1QHH/5cgeune8b9IW2tmD6NshKna5S3kI7Kdr/BZtc8DTjuGlZm8pOZYAqZYBeVio7+v3Tp7acgYh+yUK04l5Iuf6dKYkPu6M4h7czC5n+mjGATIu5tk/cLfqDu5TCknV98rfZ8Tc6VNfcmmwPSmcq0ro+Kx0yhu9iFKb6rOOPBpSieyXO3chdX7EgdAmFDS/7NSa7oGHUl91sFibCIPMFeff7I14G9NfXD515/lzI7EyjLfzpZfhD7oVsAVh7mh7Meng/90KmvyAMIFNi7rMPpdR5fme4dH2uZP2isSj5Zu5sTGShre27WSBp6tski7y1DQbOmqGBTuqrtjjnbs229plmEsdM21KWqrcWbSYr9dqxzdyhsK1Lea0y8/exX1IvKo2/y/tCIijdKC7J3em6j+t4X8h0F4wBZBq8XfYhex8qU+4bsvOmLjmwZqSZgsjEpZbfwH0r57Hdm5ea1g2y0sE7fVax1evGCONYZdYzy+jOzHiM2B7Tr6LPG2yCRbBHCvbABpG4k2sU3iqBJLYx7pYaYn5cKuvtkV+v2/bL3Z3O+0K2TXBmHeW+kA9evhsHZmbBAg7MADKFRZcrr2TvI8vzo6Xz9APbABG5V+tonTOVVtadpoGkSWY1Vp2+1ZD62rqD9uPE7bw+UxfZ4wOBbxtscUPZfvbAzKucXBJEUhZugKit6DWvJplVb+tdu3yppwqsH3HCbb1JFsIn9W4L3aw7LWXAreZXC1c0/dqUVoOgJw3a3fdhrrzScuWVOHjr5Muh3df8JEaUMt8uOGrnV0WeyILcelxx0KWTdl44tGnuu2V91ghj4oZ9xdYwtrPFyUo7S/nQ4GQY2ZHcoC6eKRSDwYFt3QtiFJV/JAspNB4uJSx1hMAhlePOq/ZgX0fs7byZDCATXkJ/34dkH/4LtR1yWCCxX9JLV5pEFdPVuE/jdUvjE+cdxFZL6bilItIn1UbiHLI7rsmCvNRX6rQy7/BH6VtycH2NFc0BxrcN87MDuGk4u70MwOELj239ZkI3vn+TZ2RB4de4F+KJdObdZiF9vIN7IdNZMwaQSXL2d533eicV5Mor57yTMaunnLxDtm3lyH7Fjhy/6x/02UauZb2d9E+zHu+4U3sSfU6nkXkT3LjenjJz8A3SsZO+wUYrbexr7GwIJN7cONj5ISuys2fOnLdj5yG/F9LDH/D3QsKCd6lwSGvcwSxkOkvGADJBzj77KAaDC/ww4j7Fmftv1lZuPWT5rb5sbWJOaO48aZND3UTmx0kdt0jrTj9x/LGD9mPIu3nZQmq3q3RtzXzdiTYzZ1PtKkV3WazpLPWaTj5uuZGt5lKn7T5YW9t901Ye6+5Grb49/Wl8XwG/yiykSqb1x0saeE+xhht3/EWi9VOdvYEMIJNagxWd7e/h9blkH0rZZ145PyhDDgskaaZgW3onX/paVyoFzkWHSGNm5Z23cbJhnrZTGkg20SfV3mavzMm8V2/UZzvZzq6/U2S7Oe9vRg/6fCcrKPWaVlZXUnQdFZDl2XK21Duypd8FCdZVCkrp9R4e1BqPVWp42H4CR7XCe5mFTH6hGEAmxPg4sPvMyy/fCa2T3/uw18WWgw4PJGUbWyozhdJ/upLzodKuSZ/InK+OlNpO5bd829s0rOrz/V3vJDCFtqW+UqfI0rFDXz+c0WnbhWBR0WmaJjJ3EN6sVmm3dm51/9qLZrZG807+SBbCu9N3QnBmfeVJvbfqPm7g3emTXQMGkEnwjbIPQPfEzwX/Z8ZLMwURBScf7KnuW9hWwSEnOlMnneirOtnEGHsQHHc0tjE4MjrYV5V5s0zfUnmq03Ua1jfIbX8TDmzRzdI2GKrT9Zd+u/bs2X2ud2o8n2u3F8JnZIUPRpcK8tvp75XTyF0yumu2jucfWtdmPWF7JftYX139qM0+xLlbBx98ohm/lHtzrON0wcALJbSMetmv/7bv+hp9XhY5WZGnAcJaVnfQabut6hMzRKd9udm7N2uC1WjqQ7uyvWnq20cGl3aGymQ+xXknatq81q2+P/0L6jS0uSJrfat92X6mBMxvp+/dxSxkkqvAADJuulqrvct4a6H1jymFXqneBYzoO7X1ck4euTzjVl3QifuXjtNLy76lKy1lZSvnqKtjbDWQOIVbDSSp3aWlJiYYHpF9vtrYag/MX3MsBliZKQWkpWxpeWnPuSefirh7Ctt/HyzjXzIL2T6/GfY8lBd4H7OQya0AA8iY2cqHdXB29QM++6hlD96bOocY/KJ35pE99UBiHWVNp+8rDtW5WFMqD4zWuj5pFTnvMLYfJwisVq9P3quO3zVN9Ll23io/dd+7amPo63SZyZhyZE9Fp1SLXvtSGPQHY/9xIclCsgx/j1dkec6deZdnZN26X347nb9aOJFFYwAZJ1at1R6Ft9nsQ0XfguU0VHWgMlOw7lHqS1ncWhxr6Ux9jdVZ6nVajJf2zb3Mqo562gaRTWGMSCaN0mDl9DlTN9VXaecHNSaGcZzORHWo9F3cUMZKyzI0sR1Tnd6y8b2vruOf8e708fGcoqajSuMuPql3MsQZQMbI9cTPn9pd9Fdvz7LswnrEqDpjP3AZNEofWsrKVrZ/GkhK5+nb2XcbcBJ9lSAUvu2XjdJsROThNSxYlYFpq/pEdRn8zJEdzY3r9dmQUY7jI4oxO7LdyoPBYy+YXy3M8AUFnBm7ciqcJAG5L+SW/jm8hfeFjB8zA8gYme6+4MQbJfuAdldepR7SjeQCQW3cMmiUvtsGgjhoGIdaNkh0psNtQZ9oiXR65x3LguOO2nmZaefkicOPZHW7pXLzwGRNC4oiO13/JBC5djW24xGs5/g/NfCn49FGLVMkcFEvx928L2T8xBlAxsT0ik/oXYOzqx+22Uep1J4Cqjs2L0+dvvQbMXuQjjW11iGXo0f6QnPreM3Xf9c/CRiRzuD0I1nd6bsxnN32yGsMgxqTEn1Bpy2kHETm+oZiRWdD/1SHGXKsf8zd6Qr/M7OQsWKdhrJMK9zM3wsZP2oGkHEw1VplF+FalWV/RUGuvBLnV3Ge5jh4vWhUm5Gkzq/s7/ync6gVnW6PIg0kQ/Q5h1zqExOcPmeWd9FG3wYy06+WPdT1WcmQMVxzeavqK1k4I3wT29icaou6hf5Wj2s0iTe5O51ZyCTITl6nxnEU+AizkPGiZgAZA88r7sWy3PehtX6VqCvdXhkIvFwyj8rJf2fBEMdv2g/XafXavtHAxop0A9xZEOmzdspf5+RLS6yTdlVhDN8uTNA2SKcU6XPtthqYzHiVMYKNQb3VakwObd0EJvhm7k4v8HlmIROEPBnVGRRuNs/I4hVZYyPMALJTlHH2oVTPZh7i3BqcvhvL1KVeN7JCgkF0aIo26HidtjoNTtLMuFQXIEoNmwemDfX5oOEUNgUDO1Nrd2m7s9Yqd7NwTt9VBZv9GKGtbVBHlHQcqtOZOpk3pXS2C78/0Hh0MgNQ6wQJXKSAk7wvZHyEGUB2yFKuvDJ3nSv1A4kq58gjl1c/rSVO39xlHjynU9Hk9E3jEJXKHmn2IApM+EoGdlIXc0o7Xd9ERSnz7Zr0GZlV65uZkY2oNM7KKrbYMJIM2nBaqlTeHEjccG4srzPv5S/vfu0V/ciosRcf/1t4WX47nVnI2NFOWqHJQvbleBfvCxkPagaQnXCUu86PnHiD2/vIzempRJ84SevhSh9ayuKmzae2mgKJ6+/8b9DuApbX6R2qSYW80OVFziQndZZV9fmsILSyGmN9yRjWkODVU6dfjlHt70wyowR9clTRZ0QJOqczbqtUYRRN+o/LQgqNJyc9FPWPnYDJQuSL39g1L6BCBpAdLLrsfQD4KKLso77vIM7QeefELybe0FhhXGLqeZ11wwKJrS5dqdMZnK/NRmxwiie6gT5nqlUxmr7g+KNxbQSQcUIMDcGlMWiUk7Cn4qRRJCsPNtAJYH1t/Ww800mVH/+0eol3p0+K7kT1Zsjwrr1HT9w00VEWRDkDyHYX2u59vLW/vv5XMqXy8huzKPQOOvaopTNO/aJzlJEdpl48b+1l9ZZir9PqKHs06azLSju9RmeZC3g1faWg8TSZCSSur9foHX86HT9OGVGlr3m5Kik3ByZbY22vBidgec/u72WnT2mrbMJ/e/gify9kwownoV7juNI4KZfeT0L9IulkANnmakv2UQAfzbLsMlERnGdFXz0j8U7fu1bbO2QpsZNu3B8Rp2m/hadDlUHEqijH8e2CjdEYtm4DfSHuOM8eBYjEwUc6RZ5mD3aOpmvULjRyMq8v9HeGh2Nngo81tn8cRIDVM2f6B887MfBznuS77IVA4Te4FzJJyhPRbe4LwXHcyL2QnfFlANkmP30U71Ba/yW/x+HVWOeeeMmQkfg29r108N4vlg41eG3T1J6CatZZ+2bvHHyiM3L6otA46orMSGuByWmpxYNU4B1/HDSaZH5+ZuhkOqk+b2OjPgvPqrJ0HF9b0du959sXAlPbCxn08E94d7pflA69axzP+riLeyE7WzMGkG3wu3pFL8ujEaC1vfJKPGLl1RRImmTGE7r+qRZxqs6xGhdpM49qwBrq+F3fUmeqT8zdUiCJ9Fmd8re0L+jzMjdwEkgCI9vXTdtJI31NfSsy0RsHGDnI8rwYrK6uPbiCqWQgYri5LwT4h8xCwuJ2peD3Qt7BLGT7S8YAsg12/R6uK7R+t3R1bs85e+flIp02e4gE0qf2Td8Igz8OOk23qpN2gSRVaSxJHbI0sH030mdbVZ2xk4o4mVLZrhSXMm+Sce5u+ESWGuJsrp4+c5rLAUygM3oimVVvw5NfBF0U63v27j0NqOnsgRgjlF7v47eZhfiV7tC7uzud94Vsf80YQLbI7qYV3ZO9D6XUCZshWK8WfJv5ph6OjHbj5lJPHJxnOnzqsUstTl4KTBCqePegMx2q7CvdrYpS5se3rljkXiLvtkdNn8hd06DPC1z3RJ/T2SSzzW0QKcex47rhTZPhfcUUNwCAwWDwrDNham/P3I/TmlnI1HiPcSCThchPMDAL2R5VBpAtcnuuhzdlwLu01iU78XySVQQHLQUri9UPz0ZKB2jaR30TncZJl21NXdS2HEsccsM3e9PWhwVp7WwsVe78tJYbw9uSOH4nTGRhbDObCjYri8E293VBRKnvZ0v5KT/21N6V0twLmRrt8Q4keyE5Ps69kO1hLZ3g9vovVC+XfdwD4FLjoKuzF+cZvre7yoqDNw7QOPfgOW2fmsP3Qcj5eafXNU7OLRk3WxnHjj4skPhaG0x2HkiqNo4WmJJgYE1ys9xeRtJbWjqzb9++F4OqKRb8XgiAl6Y4LIfaOYFMKdxo7gvhM7K2TJMBZAvIfPYBwHJzgSAJBZEjN47d63fBxR/Kez0j8Q4/1ui0GL3W4YdaJ/M6TcuGccz3OWNqXQAAEVBJREFU80TuHby8W51WR3JgROLkTQbgB/HSJODVbUz0BYONJVZfRdbEwrSO2oWObjgZoxKEXtJYfiExdVoHcnf6Ev4PAA9Na0iOMyYCGpfIk3rdjcFjUroYahhARlxnufKqAEz2UesyaiAxDj/xiOUpo4rS+ikoaSDO3Dr00oeWMq/C9k3HMb0Tpx9aW7Xuu388RtnCBrbmQOJb+Xdnn31zWlMbE6cfmRmCVSQzIcJ19yMYGxsCW39t9YWzwMtlu+mWzDOyFH6TWch0uY9hNIUMN6gLcfMYdC2UCgaQEZe738MbZe8jZB9N/VwgSaqioBH8YiTzbevfwKXGOW7fKLyXHlV0Wr2lzDezOsOoTuyzHN9K3ksHX9MXdTeO3wWwsvcQfc7Bl/r8OElPE0DdBExFc3CxWurYxG4PQPVVnn9j74vTu4S3nIkrKaVfWsdvA3ikVkdBuwloXKIUPsK707e2TAwgI/CS7EMBPyN7HyM0txvY1Ybi/ZyvsyU5CCVX1/xNvzkbSZUFTRUHL3ITSKr2mOBUFTqH7FTb2thJe0ldZoNddUplu2BfFKz86E2Zh5FJg7JjOEixmRkaVUplXz9xZIYBBMBz9+Msr8jyK9upd/nIXd87Bj4jawvLxgAyAizJPjRw44bZR1XP2E9rNWUjznm6oBG50nCqy5u142zEOH6vzYSLMnsITt5a0Ojgy1jilKSCpsyjSeZTDjPlMK5VmeW9Jx68f7YBBG4vhL8XUn5WOlPSuKQo8ONysUxnbJ6xoQwgmyyA+TBp/BwA88yrTZrXq0cIJKZTg0e0mUfqJZtkdlBxyLZt2cPJnEDedhxIjJ3lNBMnH8RDTmu5vsYOO2mbVZQGm6AkOk3mEey2o8Qy38DyAPK8971c6VPiwIMZMyqYvRDgH3EvZEYLsP1hzV7Ic0vmy+L2tSxQTwaQTRb7uR7erqElrY3c3Cadmqo32B8pHarx8CEQeDXW6fsj+z40kLhAlOg0Drk039S5dhWtDae7yiBU6ixlvr9x8ZXgIsi8g/ftDMamQFLp2xSYElk5HQcEz/UOHHysHGeGJclClvG/cS9khmuw3aHtFVk/ySxkNIAMIBtw8tmHUsr81vkGTUeuMg612lqcp4tQwS8ahxqO7DfzhiBUd9BOuXRNv8i701qxzuaMxAcnZ1ap0HV1qt0AbpDQqmkfZ1ggqdo4WmBKAokZV0GuwFrt49vOjJm/SRaiNf4xs5CZL8VWDTBZyLMKP7zVjovYngFkg1U/tYx3yuV9zrdv0HKLVSOc1gpuPvXiZiBx8OlriIP2mUdjICk1GG01nVIvest2cfZga31dGkRcT9Pct7DvVl+pU5SXxpVDlTLfX4KG/N/854QmkFhlA7kC63xgoj9l620Z6V0pvT7Ab/G+kJFotauRxiV5hp+Si2faZVj7rGEAGbYmJ3WuCtwDSWkn9RoWSNx4waGKkyy9rqn1WUJqmg0kqUyOyv5BZyRzLSazP2Jsjy2yUSCdjosMSYwoZb53knmUE0GW5UWusj99tE0BBIB5RpbG7zIL8SvYmXeThawt4/rOWDwjQxlAhoC/8lq8GVpLGhu5qiGNdyqWQBLriLzrMLlvXs9G3Gkp36DhXXQGvRUHb+qi8cvuTcHJBSbn+G3b5MCImrIHscAGwXKEEOgSFe4gGGx6hqGkoLU+nffyh7Aypd9Ej03eqMwsZCM67a7TuCQb4Gd4X8jGy8QA0sTnpM6h8XPj3PtoGiaRNQUR58hrDj/p6PcxUmFjhlIJFmUPcdKJ13bOPfLarnHd6UuF7VvameqyLZyumkp7Wqu0JdVn5fW+lWykPRvo6USYhVR4dOjQZCHyw3EdsnnqpjKANCD/wTfIfR9juPKqQfeGomGntKqBxDj71BMbh1oJAjJW3eGXwUI0pFrqAulffw13+mZM06Ecxw+SOP1EqdWXDuWCUohFztrKHEVnluVP7+njG4nKthyUWch/aYtJtGNEArIXkuNnmYUM58UAUmEjV14VCvdONfuo2CDZgHH8sdw4TuvMnSt1WUPq4EMgifu600WJyGQNzkmHuOGddBCYLiaIpN7dNBgtOEU6nQHBxjARqbAH6TBl36Spsd3bqNb7/fUnHn1kxjcQpnCTI/d7If8EwOmkggdtJyAfuxt4d/rwZWIAqbD5ZoZrZ5J9VOwwhyMEEt9ulE126/Dl30T8cpmC98emqnTcZfbgT5XV+48cSBqyBwkmfgxrlQueyTDORtfUVlkbZf9jqZf/IX53Sr+DHqMbtcwn9Y5Kqo3tjg2Av84rspqXhgEk4tKK7COyJxRdIAnHUnAZiXWjrsbJ4nY2e4gl/rRW4qGb9RnPXgYYM6wZOpVZ7dbxV0ZyeuMYUQYD3zbJSLxwhE12MwOF5/buOfhwG+5AD6Y3FNx9If+UWUgDnHaL5NkGb11XvDu9aZkYQCIqrco+Irt80XzT9wf+3Z3zqQUSX28yiybn7gNJ1FCKLktI9Bm5URQam3o3dhC64FIX29Qh1WllSd9aNmIHNiZJ5/ByfZX8hG3/4YuB50NVWwvyq4VL5vEmD7fVRNo1lMAx9PA3eHd6nQ8DiGPS2uyjumYbZCPSNPjZutc1+yplA6+4Kbi4LMHpszpT9x96N4wjVuzotJbR6UfwsxKdqUwXWJf7P2b+AMXYrA3K5lcLeV/IBoRaWyVZyNvNjcWtNXE2hjGAOO7ya4PIzO99JG5qNsuy+ahbykYiz+tPF5lTW9EwdYdv0okQkQIUHzASnTabqXh4E37qesvMowxJLmCFQUwIagh4aWDS0Kd2Hzj0e20/fRUwl1dk8VcLA5TOFC7INO5lFpKuFwMIAPeh+NRE7zpPuY/naIRN9uCTI4fvBzdBJDQQqXfQsbB07iKNa9IDW1cNTHYsq9ePa1qaQGQVljrL4OLbhk32slGwM8vyh9ZOn3rWt+3Cu1yRBYXf493pXVitxEYFjevl4aqJdMEPGEAAnFrGW+Vyvap/7MxnY9RA4rOHaGI+I6kFA6MzaigNnNMvA0kpi/vbrCPx+EZRYzZi9LrgY1uFcbxOY6NrFyxSar3or3/p1K+fOBdkXSjIFVnKPKmX94V0Yb1SG49B4T5mISWUhQ8g8mHIND4F4OISS0dL4wgkydSrmYNUlkEjCSSR3KuwGU41kPgsx7faTGfZLg52g37/1N4D+/5FZ05fldPAY7+I72gNuSLrpUjMYvsJSBZyA7OQcqEWPoCYvQ9tso+SStdLEkiq6VSUfQSXHsn8lJuyhyZZCCTVcXwgcQqNHQ3jSP/GjGSjLCfoNHefP3QGy894uzv1rpQuBvgdDXxFHuXVKdtp7AUA7uXd6faDsNgBZEVnGrgXgHwo5uvlnHYIFn52IndOP9Q5mW9i6ofIamIR2P+boBV0OLk/NmPVOpuR6nvvXmESnNwg1uOa01fPAd06feVhAPjq/fg2NP4ZgJcjMYvtJyAf5Ruzi8xp7/ZbO2ELFzqAXNbDq6b2xN0JL+RQ9SOc1jJ9jcMPIcWJmk9hmVNTyYD101pWUymX5iIzfWuBZGvZyKDf//Ndhw79Tuuevpsw2eSAWcgmgFpdfYECPsa9EGChA8iywruVUt3f+xjl35oLJElTFzSMY/cVQwJJml7YQDA0kDhdZThygSSSbxRIvCn2vQxCLgD1d+3e/cetfXhiavyGR18FXgDwO7w7fUNMbaxUhcabngH2t9G4adq00AGkKHAlgHyawGc9ljju0rE7a1xGsFEg8RvY1aBh9FUVmiBkdW+sszkjsTolK4lpSSAxKczz/bXVf/7o/ViPaztZXlFFnuGLAL7MvZBuraDKcCQDLuyW1eO3dqEDiMqwd/xIO6BxhNNawXenXtxMzgSR0EBE/hRULPSZg3X8xvd7NFGAsb1dIPH14b16Ck1hsN7/k6VDB/9jF6++CtOKCo+t4bsaZi+ET+qNuLS+qJFnWOu13s4JG7jQAUTrDjxDaZIfgFEDiXH4cXAwIaPhTnF/aqtqtA8mLokw1aXMt/aZhz/27z7LyXtLpwut/8XT5+bo8ldmIX6Zu/Z+dtBbfqVrRo/b3oUOIAp4UHX4Sp6xfRg22B+RMULo2CiQJMZUMwdf6YJGrFO0V7Icm+GEUV1nhWIw+MqevQd+v9Ob5x5F9O7uC/l9XpEVQWl3US69fi7/Fr7ZbjMnb91iB5AlPDTQeHTymLsxgvmmXzXVOffaKahKO+v0U6HPHFKp2ccwQaOmM4oZpi4KLFneO93btesPvvoIXqzp67pAKZ3n+MfuvpCuz2YR7F/TGr/71JE52Ifb4WotdACR32jIM3xBAWd2yHF+uo/jtFZCw++P1BINK5AEJGQkw09rSfahB/hNfFENEvVzciB7Ie6KrPY/mn5OmG9zGpJ9fFkC/rxlwtvhsdABRDZi83X871D4LQCr2wE4t31GCCRm7k2ntZpOibkwYTKVBFoZNMoEpJRJU5VlX1ta2vWr7rLXpPfcHKyo4pI+/het8Zu8rLe1qyrB42sA/vvHfknNXya8Dezlv9ltdJ6XLuaxBBcUfyfLsg8DOIoFvz+maV21bnjihpMlNZV2Wp7UkTSw2hv1SVWqs99bWn5EF/1PP/W3e38wL1deNfH1shP3Pbtn75ETvwDgHgDHQnLmG/B9FgTkE7wGQB6A+cknfhlfXoTP4iigGUAcJbmrVJ7Kax6sqPE6AEcALLvqmFPsDmO5NB1WN0y+0z7jGH9LNhRFYZBkmTwFBiiKomqDqR/2pxjY/sPqRT4Y9F9Z2rX7Oejij3SRfc489kN+0mdBXvJZlF/HLBQ+BYVro89ilXXMROrk2L97Wpv18e2qurxc3n3dRrqkXVzv+1Tlsb5q3Tj77FSX2CYzehkapwD8waCPf/DVFcVTjJaMwxMdLHxRa3XTA8i/AVyoe7hcD7An76V36/ez8I8JvSL5B4NhdcPkwntY3TB53Kc6/kZ1Xt+4+5jPzFo/OA4lVNxLy+zcS+TxsZfLe9zHHOc43e/hcXO57ooK+uI+i1CWQPIscDjv4cqtfBb9Wguj6noPqxsm95ylvqpL6ny/at0wedynap/vU5Vvp884dBVAHwO8oHbh6cfP4hXuechK8EUCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJDA1Av8/CxV7KAQmBkgAAAAASUVORK5CYII=" - /> + <linearGradient + id="grd1" + gradientUnits="userSpaceOnUse" + x1="683.501" + y1="852.856" + x2="272.73" + y2="1564.343" + > + <stop offset="0" stopColor="#0052cc" /> + <stop offset="0.923" stopColor="#2684ff" /> + </linearGradient> </defs> + <g id="Layer_2"> + <g id="Blue"> + <path + fill="url(#grd1)" + d="M470.2 732.75C465.49 726.58 459.27 721.73 452.14 718.67C445.01 715.61 437.21 714.44 429.5 715.28C421.79 716.12 414.42 718.94 408.12 723.46C401.81 727.98 396.77 734.06 393.5 741.1L4.86 1519.33C1.32 1526.42 -0.35 1534.3 0 1542.22C0.36 1550.15 2.73 1557.85 6.89 1564.59C11.05 1571.34 16.87 1576.9 23.78 1580.77C30.7 1584.63 38.49 1586.65 46.4 1586.66L587.56 1586.66C591.87 1586.76 596.18 1586.23 600.34 1585.1C604.5 1583.97 608.48 1582.24 612.14 1579.97C615.81 1577.7 619.13 1574.91 622 1571.69C624.88 1568.46 627.27 1564.84 629.11 1560.94C745.84 1319.35 675.09 952.01 470.2 732.75Z" + /> + <path + fill="#2684ff" + d="M755.24 24.92C707.03 99.22 668.62 179.46 640.98 263.64C613.34 347.83 596.7 435.24 591.48 523.7C586.26 612.16 592.49 700.93 610.03 787.79C627.57 874.64 656.27 958.86 695.41 1038.34L956.3 1560.94C958.22 1564.78 960.67 1568.34 963.56 1571.52C966.46 1574.69 969.78 1577.45 973.43 1579.71C977.08 1581.97 981.03 1583.71 985.15 1584.88C989.28 1586.06 993.55 1586.66 997.85 1586.66L1538.91 1586.66C1546.83 1586.65 1554.61 1584.63 1561.53 1580.77C1568.44 1576.9 1574.26 1571.34 1578.42 1564.59C1582.58 1557.85 1584.95 1550.15 1585.31 1542.22C1585.67 1534.3 1583.99 1526.42 1580.45 1519.33C1576.91 1512.24 852.54 61.1 834.25 24.62C830.63 17.2 825 10.95 817.99 6.58C810.99 2.22 802.9 -0.08 794.65 -0.05C786.41 -0.02 778.33 2.34 771.36 6.76C764.39 11.17 758.8 17.47 755.24 24.92L755.24 24.92Z" + /> + </g> + </g> </svg> ); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/index.ts index e2b7a6998bda3..76a19836b0cda 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/pagerduty/index.ts @@ -223,6 +223,20 @@ async function executor( }; } + if (response == null) { + const message = i18n.translate( + 'xpack.stackConnectors.pagerduty.unexpectedNullResponseErrorMessage', + { + defaultMessage: 'unexpected null response from pagerduty', + } + ); + return { + status: 'error', + actionId, + message, + }; + } + logger.debug(`response posting pagerduty event: ${response.status}`); if (response.status === 202) { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/stack/teams/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/teams/index.ts index df44d568a2f30..e2c7502311597 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/stack/teams/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/teams/index.ts @@ -136,6 +136,10 @@ async function teamsExecutor( }) ); + if (result == null) { + return errorResultUnexpectedNullResponse(actionId); + } + if (isOk(result)) { const { value: { status, statusText, data: responseData, headers: responseHeaders }, @@ -206,6 +210,17 @@ function errorResultInvalid( }; } +function errorResultUnexpectedNullResponse(actionId: string): ConnectorTypeExecutorResult<void> { + const message = i18n.translate('xpack.stackConnectors.teams.unexpectedNullResponseErrorMessage', { + defaultMessage: 'unexpected null response from Microsoft Teams', + }); + return { + status: 'error', + actionId, + message, + }; +} + function retryResult(actionId: string, message: string): ConnectorTypeExecutorResult<void> { const errMessage = i18n.translate( 'xpack.stackConnectors.teams.errorPostingRetryLaterErrorMessage', diff --git a/x-pack/plugins/stack_connectors/server/connector_types/stack/webhook/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/webhook/index.ts index 0656cb0692e37..d51ed935980bd 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/stack/webhook/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/webhook/index.ts @@ -184,6 +184,10 @@ export async function executor( }) ); + if (result == null) { + return errorResultUnexpectedNullResponse(actionId); + } + if (isOk(result)) { const { value: { status, statusText }, @@ -280,6 +284,20 @@ function errorResultUnexpectedError(actionId: string): ConnectorTypeExecutorResu }; } +function errorResultUnexpectedNullResponse(actionId: string): ConnectorTypeExecutorResult<void> { + const message = i18n.translate( + 'xpack.stackConnectors.webhook.unexpectedNullResponseErrorMessage', + { + defaultMessage: 'unexpected null response from webhook', + } + ); + return { + status: 'error', + actionId, + message, + }; +} + function retryResult(actionId: string, serviceMessage: string): ConnectorTypeExecutorResult<void> { const errMessage = i18n.translate( 'xpack.stackConnectors.webhook.invalidResponseRetryLaterErrorMessage', diff --git a/x-pack/plugins/stack_connectors/server/connector_types/stack/xmatters/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/stack/xmatters/index.ts index b34e300a496ed..7a3d701b96242 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/stack/xmatters/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/stack/xmatters/index.ts @@ -274,6 +274,20 @@ export async function executor( }; } + if (result == null) { + const message = i18n.translate( + 'xpack.stackConnectors.xmatters.unexpectedNullResponseErrorMessage', + { + defaultMessage: 'unexpected null response from xmatters', + } + ); + return { + status: 'error', + actionId, + message, + }; + } + if (result.status >= 200 && result.status < 300) { const { status, statusText } = result; logger.debug(`Response from xMatters action "${actionId}": [HTTP ${status}] ${statusText}`); diff --git a/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts b/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts index 5aa7ee96f25b1..648337218979c 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/data_view_permissions.ts @@ -48,10 +48,10 @@ journey('DataViewPermissions', async ({ page, params }) => { step('Click explore data button', async () => { await page.click(byTestId('uptimeExploreDataButton')); await waitForLoadingToFinish({ page }); - await page.waitForSelector(`text=${permissionError}`, TIMEOUT_60_SEC); - expect(await page.$(`text=${permissionError}`)).toBeTruthy(); }); -}); -const permissionError = - "Unable to create Data View. You don't have the required permission, please contact your admin."; + step('it renders for viewer user as well', async () => { + await page.waitForSelector(`text=browser`, TIMEOUT_60_SEC); + expect(await page.$(`text=Monitor duration`)).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/failed_tests_count.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/failed_tests_count.tsx new file mode 100644 index 0000000000000..3316dbd404e57 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/failed_tests_count.tsx @@ -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 { useKibana } from '@kbn/kibana-react-plugin/public'; +import React from 'react'; +import { ClientPluginsStart } from '../../../../../plugin'; +import { KpiWrapper } from '../monitor_summary/kpi_wrapper'; +import { useMonitorQueryId } from '../hooks/use_monitor_query_id'; + +export const FailedTestsCount = (time: { to: string; from: string }) => { + const { observability } = useKibana<ClientPluginsStart>().services; + + const { ExploratoryViewEmbeddable } = observability; + + const monitorId = useMonitorQueryId(); + + return ( + <KpiWrapper> + <ExploratoryViewEmbeddable + reportType="single-metric" + attributes={[ + { + time, + reportDefinitions: { + 'monitor.id': [monitorId], + }, + dataType: 'synthetics', + selectedMetricField: 'monitor_failed_tests', + name: 'synthetics-series-1', + }, + ]} + /> + </KpiWrapper> + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx index 246e4ea19b94d..fba0664f0ef07 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx @@ -14,6 +14,7 @@ import { } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; +import { FailedTestsCount } from './failed_tests_count'; import { useGetUrlParams } from '../../../hooks'; import { SyntheticsDatePicker } from '../../common/date_picker/synthetics_date_picker'; import { MonitorErrorsCount } from '../monitor_summary/monitor_errors_count'; @@ -34,7 +35,14 @@ export const MonitorErrors = () => { <EuiTitle size="xs"> <h3 css={{ margin: euiTheme.size.s, marginBottom: 0 }}>{OVERVIEW_LABEL}</h3> </EuiTitle> - <MonitorErrorsCount to={dateRangeEnd} from={dateRangeStart} /> + <EuiFlexGroup> + <EuiFlexItem> + <MonitorErrorsCount to={dateRangeEnd} from={dateRangeStart} /> + </EuiFlexItem> + <EuiFlexItem> + <FailedTestsCount to={dateRangeEnd} from={dateRangeStart} /> + </EuiFlexItem> + </EuiFlexGroup> </EuiPanel> </EuiFlexItem> <EuiFlexItem grow={3}> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx index c4809274da190..30a220f3f10b8 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx @@ -8,9 +8,9 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import React from 'react'; import { ReportTypes } from '@kbn/observability-plugin/public'; -import { useParams } from 'react-router-dom'; import { KpiWrapper } from './kpi_wrapper'; import { ClientPluginsStart } from '../../../../../plugin'; +import { useMonitorQueryId } from '../hooks/use_monitor_query_id'; interface MonitorErrorsCountProps { from: string; @@ -22,7 +22,7 @@ export const MonitorErrorsCount = (props: MonitorErrorsCountProps) => { const { ExploratoryViewEmbeddable } = observability; - const { monitorId } = useParams<{ monitorId: string }>(); + const monitorId = useMonitorQueryId(); return ( <KpiWrapper> @@ -34,7 +34,7 @@ export const MonitorErrorsCount = (props: MonitorErrorsCountProps) => { time: props, reportDefinitions: { config_id: [monitorId] }, dataType: 'synthetics', - selectedMetricField: 'state.id', + selectedMetricField: 'monitor_errors', name: 'synthetics-series-1', }, ]} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_status.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_status.tsx index fd7583fe02fab..065b6a08f23e3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_status.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_status.tsx @@ -9,8 +9,13 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiStat, EuiTitle } fro import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { clearOverviewStatusErrorAction, selectOverviewStatus } from '../../../../state'; +import { + clearOverviewStatusErrorAction, + fetchOverviewStatusAction, + selectOverviewStatus, +} from '../../../../state'; import { kibanaService } from '../../../../../../utils/kibana_service'; +import { useSyntheticsRefreshContext } from '../../../../contexts'; function title(t?: number) { return t ?? '-'; @@ -20,6 +25,12 @@ export function OverviewStatus() { const { status, statusError } = useSelector(selectOverviewStatus); const dispatch = useDispatch(); + const { lastRefresh } = useSyntheticsRefreshContext(); + + useEffect(() => { + dispatch(fetchOverviewStatusAction.get()); + }, [dispatch, lastRefresh]); + useEffect(() => { if (statusError) { dispatch(clearOverviewStatusErrorAction()); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx index 893850e76cb84..4eb864e7a2a3c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx @@ -14,7 +14,6 @@ import { useEnablement } from '../../../hooks'; import { useSyntheticsRefreshContext } from '../../../contexts/synthetics_refresh_context'; import { fetchMonitorOverviewAction, - fetchOverviewStatusAction, selectOverviewState, selectServiceLocationsState, } from '../../../state'; @@ -53,7 +52,6 @@ export const OverviewPage: React.FC = () => { useEffect(() => { dispatch(fetchMonitorOverviewAction.get(pageState)); - dispatch(fetchOverviewStatusAction.get()); }, [dispatch, pageState]); const { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx index 3580df2c317a7..6d8b0b455a5f2 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx @@ -135,7 +135,7 @@ const defaultState = { describe('WaterfallChartContainer', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); it('does not display waterfall chart unavailable when isWaterfallSupported is true', () => { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx index 81ed2d024340c..ba26366644dbd 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx @@ -26,7 +26,7 @@ const getHighLightedItems = (query: string, filters: string[]) => { describe('WaterfallChartWrapper', () => { beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); it('renders the correct sidebar items', () => { diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.test.tsx index 42cb46427011c..c36e46b9d999b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.test.tsx @@ -19,7 +19,7 @@ import { } from '../../waterfall/components/translations'; describe('waterfall filter', () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); it('renders correctly', () => { const { getByLabelText, getByTitle } = render( diff --git a/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.test.ts b/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.test.ts new file mode 100644 index 0000000000000..ac71653c31c8f --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.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 uuid from 'uuid'; +import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { bulkRemoveIfExist } from './bulk_remove_if_exist'; +import { taskStoreMock } from '../task_store.mock'; + +describe('removeIfExists', () => { + const ids = [uuid.v4(), uuid.v4()]; + + test('removes the tasks by its IDs', async () => { + const ts = taskStoreMock.create({}); + + expect(await bulkRemoveIfExist(ts, ids)).toBe(undefined); + expect(ts.bulkRemove).toHaveBeenCalledWith(ids); + }); + + test('handles 404 errors caused by the task not existing', async () => { + const ts = taskStoreMock.create({}); + + ts.bulkRemove.mockRejectedValue( + SavedObjectsErrorHelpers.createGenericNotFoundError('task', ids[0]) + ); + + expect(await bulkRemoveIfExist(ts, ids)).toBe(undefined); + expect(ts.bulkRemove).toHaveBeenCalledWith(ids); + }); + + test('throws if any other error is caused by task removal', async () => { + const ts = taskStoreMock.create({}); + + const error = SavedObjectsErrorHelpers.createInvalidVersionError(uuid.v4()); + ts.bulkRemove.mockRejectedValue(error); + + expect(bulkRemoveIfExist(ts, ids)).rejects.toBe(error); + expect(ts.bulkRemove).toHaveBeenCalledWith(ids); + }); +}); diff --git a/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.ts b/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.ts new file mode 100644 index 0000000000000..c3c1a61868e60 --- /dev/null +++ b/x-pack/plugins/task_manager/server/lib/bulk_remove_if_exist.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 { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { TaskStore } from '../task_store'; + +/** + * Removes a task from the store, ignoring a not found error + * Other errors are re-thrown + * + * @param taskStore + * @param taskIds + */ +export async function bulkRemoveIfExist(taskStore: TaskStore, taskIds: string[]) { + try { + return await taskStore.bulkRemove(taskIds); + } catch (err) { + if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { + throw err; + } + } +} diff --git a/x-pack/plugins/task_manager/server/mocks.ts b/x-pack/plugins/task_manager/server/mocks.ts index 6cf405c4a1b28..97ebb227d17c0 100644 --- a/x-pack/plugins/task_manager/server/mocks.ts +++ b/x-pack/plugins/task_manager/server/mocks.ts @@ -27,6 +27,7 @@ const createStartMock = () => { ephemeralRunNow: jest.fn(), ensureScheduled: jest.fn(), removeIfExists: jest.fn(), + bulkRemoveIfExist: jest.fn(), supportsEphemeralTasks: jest.fn(), bulkUpdateSchedules: jest.fn(), bulkSchedule: jest.fn(), diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index e4cf8730f6dbc..d16fab4a48e20 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -18,10 +18,12 @@ import { ServiceStatusLevels, CoreStatus, } from '@kbn/core/server'; +import type { SavedObjectsBulkDeleteResponse } from '@kbn/core/server'; import { TaskPollingLifecycle } from './polling_lifecycle'; import { TaskManagerConfig } from './config'; import { createInitialMiddleware, addMiddlewareToChain, Middleware } from './lib/middleware'; import { removeIfExists } from './lib/remove_if_exists'; +import { bulkRemoveIfExist } from './lib/bulk_remove_if_exist'; import { setupSavedObjects } from './saved_objects'; import { TaskDefinitionRegistry, TaskTypeDictionary, REMOVED_TYPES } from './task_type_dictionary'; import { AggregationOpts, FetchResult, SearchOpts, TaskStore } from './task_store'; @@ -33,6 +35,7 @@ import { EphemeralTaskLifecycle } from './ephemeral_task_lifecycle'; import { EphemeralTask, ConcreteTaskInstance } from './task'; import { registerTaskManagerUsageCollector } from './usage'; import { TASK_MANAGER_INDEX } from './constants'; + export interface TaskManagerSetupContract { /** * @deprecated @@ -59,7 +62,11 @@ export type TaskManagerStartContract = Pick< > & Pick<TaskStore, 'fetch' | 'aggregate' | 'get' | 'remove'> & { removeIfExists: TaskStore['remove']; - } & { supportsEphemeralTasks: () => boolean }; + } & { + bulkRemoveIfExist: (ids: string[]) => Promise<SavedObjectsBulkDeleteResponse | undefined>; + } & { + supportsEphemeralTasks: () => boolean; + }; export class TaskManagerPlugin implements Plugin<TaskManagerSetupContract, TaskManagerStartContract> @@ -248,6 +255,7 @@ export class TaskManagerPlugin taskStore.aggregate(opts), get: (id: string) => taskStore.get(id), remove: (id: string) => taskStore.remove(id), + bulkRemoveIfExist: (ids: string[]) => bulkRemoveIfExist(taskStore, ids), removeIfExists: (id: string) => removeIfExists(taskStore, id), schedule: (...args) => taskScheduling.schedule(...args), bulkSchedule: (...args) => taskScheduling.bulkSchedule(...args), diff --git a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts index cdfebd1156c55..c57c1b2b7bbc3 100644 --- a/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts +++ b/x-pack/plugins/task_manager/server/polling/delay_on_claim_conflicts.test.ts @@ -16,7 +16,7 @@ import { TaskLifecycleEvent } from '../polling_lifecycle'; import { FillPoolResult } from '../lib/fill_pool'; describe('delayOnClaimConflicts', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); test( 'initializes with a delay of 0', diff --git a/x-pack/plugins/task_manager/server/polling/task_poller.test.ts b/x-pack/plugins/task_manager/server/polling/task_poller.test.ts index f01ea2f018cb9..396cef0cfa7a6 100644 --- a/x-pack/plugins/task_manager/server/polling/task_poller.test.ts +++ b/x-pack/plugins/task_manager/server/polling/task_poller.test.ts @@ -14,7 +14,7 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { asOk, asErr } from '../lib/result_type'; describe('TaskPoller', () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => jest.useFakeTimers('legacy')); test( 'intializes the poller with the provided interval', diff --git a/x-pack/plugins/task_manager/server/task_store.mock.ts b/x-pack/plugins/task_manager/server/task_store.mock.ts index 4e9c5dda39527..cc9aac708b2de 100644 --- a/x-pack/plugins/task_manager/server/task_store.mock.ts +++ b/x-pack/plugins/task_manager/server/task_store.mock.ts @@ -20,6 +20,7 @@ export const taskStoreMock = { schedule: jest.fn(), bulkSchedule: jest.fn(), bulkUpdate: jest.fn(), + bulkRemove: jest.fn(), get: jest.fn(), getLifecycle: jest.fn(), fetch: jest.fn(), diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index 561d4ac6a0989..7bc731a0d8b6b 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -25,6 +25,8 @@ import { mockLogger } from './test_utils'; const savedObjectsClient = savedObjectsRepositoryMock.create(); const serializer = savedObjectsServiceMock.createSerializer(); +const randomId = () => `id-${_.random(1, 20)}`; + beforeEach(() => jest.resetAllMocks()); const mockedDate = new Date('2019-02-12T21:01:22.479Z'); @@ -529,6 +531,41 @@ describe('TaskStore', () => { }); }); + describe('bulkRemove', () => { + let store: TaskStore; + + const tasksIdsToDelete = [randomId(), randomId()]; + + beforeAll(() => { + store = new TaskStore({ + index: 'tasky', + taskManagerId: '', + serializer, + esClient: elasticsearchServiceMock.createClusterClient().asInternalUser, + definitions: taskDefinitions, + savedObjectsRepository: savedObjectsClient, + }); + }); + + test('removes the tasks with the specified ids', async () => { + const result = await store.bulkRemove(tasksIdsToDelete); + expect(result).toBeUndefined(); + expect(savedObjectsClient.bulkDelete).toHaveBeenCalledWith([ + { type: 'task', id: tasksIdsToDelete[0] }, + { type: 'task', id: tasksIdsToDelete[1] }, + ]); + }); + + test('pushes error from saved objects client to errors$', async () => { + const firstErrorPromise = store.errors$.pipe(first()).toPromise(); + savedObjectsClient.bulkDelete.mockRejectedValue(new Error('Failure')); + await expect(store.bulkRemove(tasksIdsToDelete)).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failure"` + ); + expect(await firstErrorPromise).toMatchInlineSnapshot(`[Error: Failure]`); + }); + }); + describe('get', () => { let store: TaskStore; @@ -802,5 +839,3 @@ describe('TaskStore', () => { }); }); }); - -const randomId = () => `id-${_.random(1, 20)}`; diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index 5d1a7246440f4..c2c003ff6f7bb 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -12,6 +12,7 @@ import { Subject } from 'rxjs'; import { omit, defaults } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { SavedObjectsBulkDeleteResponse } from '@kbn/core/server'; import { SavedObject, @@ -294,6 +295,22 @@ export class TaskStore { } } + /** + * Bulk removes the specified tasks from the index. + * + * @param {SavedObjectsBulkDeleteObject[]} savedObjectsToDelete + * @returns {Promise<SavedObjectsBulkDeleteResponse>} + */ + public async bulkRemove(taskIds: string[]): Promise<SavedObjectsBulkDeleteResponse> { + try { + const savedObjectsToDelete = taskIds.map((taskId) => ({ id: taskId, type: 'task' })); + return await this.savedObjectsRepository.bulkDelete(savedObjectsToDelete); + } catch (e) { + this.errors$.next(e); + throw e; + } + } + /** * Gets a task by id * diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts index 17c4d79e60dcc..4de89a37f8833 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts @@ -53,6 +53,15 @@ describe('Indicators', () => { esArchiverUnload('threat_intelligence'); }); + describe('Indicators page loading', () => { + it('verify the fleet plugin integrations endpoint exists', () => { + cy.request({ + method: 'GET', + url: '/api/fleet/epm/packages', + }).should((response) => expect(response.status).to.eq(200)); + }); + }); + describe('Indicators page basics', () => { before(() => { cy.visit(THREAT_INTELLIGENCE); diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts index e2a0459dc3fd6..4c55a0505d34c 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/query_bar.cy.ts @@ -6,23 +6,23 @@ */ import { - INDICATOR_TYPE_CELL, - TOGGLE_FLYOUT_BUTTON, - FLYOUT_CLOSE_BUTTON, - KQL_FILTER, - INDICATORS_TABLE_CELL_FILTER_IN_BUTTON, - INDICATORS_TABLE_CELL_FILTER_OUT_BUTTON, - FLYOUT_TABLE_TAB_ROW_FILTER_IN_BUTTON, - FLYOUT_TABLE_TAB_ROW_FILTER_OUT_BUTTON, - BARCHART_POPOVER_BUTTON, BARCHART_FILTER_IN_BUTTON, BARCHART_FILTER_OUT_BUTTON, + BARCHART_POPOVER_BUTTON, + FLYOUT_CLOSE_BUTTON, FLYOUT_OVERVIEW_TAB_BLOCKS_FILTER_IN_BUTTON, FLYOUT_OVERVIEW_TAB_BLOCKS_FILTER_OUT_BUTTON, + FLYOUT_OVERVIEW_TAB_BLOCKS_ITEM, FLYOUT_OVERVIEW_TAB_TABLE_ROW_FILTER_IN_BUTTON, FLYOUT_OVERVIEW_TAB_TABLE_ROW_FILTER_OUT_BUTTON, - FLYOUT_OVERVIEW_TAB_BLOCKS_ITEM, + FLYOUT_TABLE_TAB_ROW_FILTER_IN_BUTTON, + FLYOUT_TABLE_TAB_ROW_FILTER_OUT_BUTTON, FLYOUT_TABS, + INDICATOR_TYPE_CELL, + INDICATORS_TABLE_CELL_FILTER_IN_BUTTON, + INDICATORS_TABLE_CELL_FILTER_OUT_BUTTON, + KQL_FILTER, + TOGGLE_FLYOUT_BUTTON, } from '../screens/indicators'; import { selectRange } from '../tasks/select_range'; import { login } from '../tasks/login'; diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts index de3d0adf72c81..eb24ecaaa5a5f 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/timeline.cy.ts @@ -9,18 +9,18 @@ import { BARCHART_POPOVER_BUTTON, BARCHART_TIMELINE_BUTTON, FLYOUT_CLOSE_BUTTON, + FLYOUT_OVERVIEW_TAB_BLOCKS_ITEM, + FLYOUT_OVERVIEW_TAB_BLOCKS_TIMELINE_BUTTON, FLYOUT_OVERVIEW_TAB_TABLE_ROW_TIMELINE_BUTTON, FLYOUT_TABLE_TAB_ROW_TIMELINE_BUTTON, FLYOUT_TABS, + INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON, INDICATOR_TYPE_CELL, INDICATORS_TABLE_CELL_TIMELINE_BUTTON, + INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON, TIMELINE_DRAGGABLE_ITEM, TOGGLE_FLYOUT_BUTTON, UNTITLED_TIMELINE_BUTTON, - FLYOUT_OVERVIEW_TAB_BLOCKS_TIMELINE_BUTTON, - FLYOUT_OVERVIEW_TAB_BLOCKS_ITEM, - INDICATORS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_ICON, - INDICATOR_FLYOUT_INVESTIGATE_IN_TIMELINE_BUTTON, } from '../screens/indicators'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { login } from '../tasks/login'; diff --git a/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts b/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts index f33034dccb9c5..accbed00a47e9 100644 --- a/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts +++ b/x-pack/plugins/threat_intelligence/cypress/tasks/login.ts @@ -5,8 +5,8 @@ * 2.0. */ -import Url from 'url'; import type { UrlObject } from 'url'; +import Url from 'url'; import * as yaml from 'js-yaml'; diff --git a/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard/enterprise_guard.tsx b/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard/enterprise_guard.tsx index 370c28e8d50f9..e87edb2a35162 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard/enterprise_guard.tsx +++ b/x-pack/plugins/threat_intelligence/public/containers/enterprise_guard/enterprise_guard.tsx @@ -7,7 +7,8 @@ import React, { FC } from 'react'; import { Paywall } from '../../components/paywall'; -import { useKibana, useSecurityContext } from '../../hooks'; +import { useKibana } from '../../hooks/use_kibana'; +import { useSecurityContext } from '../../hooks/use_security_context'; import { SecuritySolutionPluginTemplateWrapper } from '../security_solution_plugin_template_wrapper'; export const EnterpriseGuard: FC = ({ children }) => { diff --git a/x-pack/plugins/threat_intelligence/public/containers/indicators_page_wrapper.tsx b/x-pack/plugins/threat_intelligence/public/containers/indicators_page_wrapper.tsx new file mode 100644 index 0000000000000..f5fe0d496ace3 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/containers/indicators_page_wrapper.tsx @@ -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 React, { Suspense, VFC } from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { IndicatorsPage } from '../modules/indicators/pages'; +import { SecuritySolutionPluginTemplateWrapper } from './security_solution_plugin_template_wrapper'; + +export const IndicatorsPageWrapper: VFC = () => { + const queryClient = new QueryClient(); + + return ( + <QueryClientProvider client={queryClient}> + <SecuritySolutionPluginTemplateWrapper> + <Suspense fallback={<div />}> + <IndicatorsPage /> + </Suspense> + </SecuritySolutionPluginTemplateWrapper> + </QueryClientProvider> + ); +}; + +// Note: This is for lazy loading +// eslint-disable-next-line import/no-default-export +export default IndicatorsPageWrapper; diff --git a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/__snapshots__/integrations_guard.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/__snapshots__/integrations_guard.test.tsx.snap index 95785cffd6ad3..1fcca470c6f15 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/__snapshots__/integrations_guard.test.tsx.snap +++ b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/__snapshots__/integrations_guard.test.tsx.snap @@ -1,24 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`checking if the page should be visible (based on indicator count) when indicator count is being loaded should render nothing at all: loading 1`] = ` -<DocumentFragment> - <span - aria-label="Loading" - class="euiLoadingLogo emotion-euiLoadingLogo-xl" - role="progressbar" - > - <span - class="emotion-euiLoadingLogo__icon" - > - <span - data-euiicon-type="logoSecurity" - /> - </span> - </span> -</DocumentFragment> -`; - -exports[`checking if the page should be visible (based on indicator count) when indicator count is loaded and there are no indicators should render empty page when no indicators are found: no indicators 1`] = ` +exports[`IntegrationsGuard should render empty page when no indicators are found and no ti integrations are installed 1`] = ` <DocumentFragment> <div class="euiPanel euiPanel--transparent euiEmptyPrompt euiEmptyPrompt--horizontal euiEmptyPrompt--paddingLarge emotion-euiPanel-m-transparent" @@ -118,8 +100,68 @@ exports[`checking if the page should be visible (based on indicator count) when </DocumentFragment> `; -exports[`checking if the page should be visible (based on indicator count) when loading is done and we have some indicators should render indicators table: indicators are present 1`] = ` +exports[`IntegrationsGuard should render indicators page when we have some ti integrations installed 1`] = ` <DocumentFragment> should be restricted </DocumentFragment> `; + +exports[`IntegrationsGuard should render indicators table when we have some indicators 1`] = ` +<DocumentFragment> + should be restricted +</DocumentFragment> +`; + +exports[`IntegrationsGuard should render loading when indicator count and integrations are being loaded 1`] = ` +<DocumentFragment> + <span + aria-label="Loading" + class="euiLoadingLogo emotion-euiLoadingLogo-xl" + role="progressbar" + > + <span + class="emotion-euiLoadingLogo__icon" + > + <span + data-euiicon-type="logoSecurity" + /> + </span> + </span> +</DocumentFragment> +`; + +exports[`IntegrationsGuard should render loading when indicator only is loading 1`] = ` +<DocumentFragment> + <span + aria-label="Loading" + class="euiLoadingLogo emotion-euiLoadingLogo-xl" + role="progressbar" + > + <span + class="emotion-euiLoadingLogo__icon" + > + <span + data-euiicon-type="logoSecurity" + /> + </span> + </span> +</DocumentFragment> +`; + +exports[`IntegrationsGuard should render loading when integrations only are loading 1`] = ` +<DocumentFragment> + <span + aria-label="Loading" + class="euiLoadingLogo emotion-euiLoadingLogo-xl" + role="progressbar" + > + <span + class="emotion-euiLoadingLogo__icon" + > + <span + data-euiicon-type="logoSecurity" + /> + </span> + </span> +</DocumentFragment> +`; diff --git a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx index ce8b3b9aaaa98..b0976721311fc 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx @@ -5,76 +5,164 @@ * 2.0. */ +import { UseQueryResult } from '@tanstack/react-query'; import { render } from '@testing-library/react'; import React from 'react'; import { IntegrationsGuard } from '.'; import { TestProvidersComponent } from '../../common/mocks/test_providers'; -import { useIntegrationsPageLink, useTIDocumentationLink } from '../../hooks'; +import { + Integration, + useIntegrations, + useIntegrationsPageLink, + useTIDocumentationLink, +} from '../../hooks'; import { useIndicatorsTotalCount } from '../../modules/indicators'; +import { INSTALLATION_STATUS, THREAT_INTELLIGENCE_CATEGORY } from '../../utils'; jest.mock('../../modules/indicators/hooks/use_total_count'); jest.mock('../../hooks/use_integrations_page_link'); jest.mock('../../hooks/use_documentation_link'); +jest.mock('../../hooks/use_integrations'); -describe('checking if the page should be visible (based on indicator count)', () => { - describe('when indicator count is being loaded', () => { - it('should render nothing at all', () => { - ( - useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount> - ).mockReturnValue({ - count: 0, - isLoading: true, - }); - ( - useIntegrationsPageLink as jest.MockedFunction<typeof useIntegrationsPageLink> - ).mockReturnValue(''); - ( - useTIDocumentationLink as jest.MockedFunction<typeof useTIDocumentationLink> - ).mockReturnValue(''); - - const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, { - wrapper: TestProvidersComponent, - }); - - expect(asFragment()).toMatchSnapshot('loading'); +describe('IntegrationsGuard', () => { + it('should render loading when indicator count and integrations are being loaded', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount> + ).mockReturnValue({ + count: 0, + isLoading: true, }); + ( + useIntegrationsPageLink as jest.MockedFunction<typeof useIntegrationsPageLink> + ).mockReturnValue(''); + (useTIDocumentationLink as jest.MockedFunction<typeof useTIDocumentationLink>).mockReturnValue( + '' + ); + (useIntegrations as jest.MockedFunction<typeof useIntegrations>).mockReturnValue({ + isLoading: true, + data: [], + } as unknown as UseQueryResult<Integration[]>); + + const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, { + wrapper: TestProvidersComponent, + }); + + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render loading when indicator only is loading', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount> + ).mockReturnValue({ + count: 0, + isLoading: true, + }); + ( + useIntegrationsPageLink as jest.MockedFunction<typeof useIntegrationsPageLink> + ).mockReturnValue(''); + (useTIDocumentationLink as jest.MockedFunction<typeof useTIDocumentationLink>).mockReturnValue( + '' + ); + (useIntegrations as jest.MockedFunction<typeof useIntegrations>).mockReturnValue({ + isLoading: false, + data: [], + } as unknown as UseQueryResult<Integration[]>); + + const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, { + wrapper: TestProvidersComponent, + }); + + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render loading when integrations only are loading', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount> + ).mockReturnValue({ + count: 0, + isLoading: true, + }); + ( + useIntegrationsPageLink as jest.MockedFunction<typeof useIntegrationsPageLink> + ).mockReturnValue(''); + (useTIDocumentationLink as jest.MockedFunction<typeof useTIDocumentationLink>).mockReturnValue( + '' + ); + (useIntegrations as jest.MockedFunction<typeof useIntegrations>).mockReturnValue({ + isLoading: true, + data: [], + } as unknown as UseQueryResult<Integration[]>); + + const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, { + wrapper: TestProvidersComponent, + }); + + expect(asFragment()).toMatchSnapshot(); + }); + + it('should render empty page when no indicators are found and no ti integrations are installed', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount> + ).mockReturnValue({ + count: 0, + isLoading: false, + }); + ( + useIntegrationsPageLink as jest.MockedFunction<typeof useIntegrationsPageLink> + ).mockReturnValue(''); + (useTIDocumentationLink as jest.MockedFunction<typeof useTIDocumentationLink>).mockReturnValue( + '' + ); + (useIntegrations as jest.MockedFunction<typeof useIntegrations>).mockReturnValue({ + isLoading: false, + data: [], + } as unknown as UseQueryResult<Integration[]>); + + const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, { + wrapper: TestProvidersComponent, + }); + expect(asFragment()).toMatchSnapshot(); }); - describe('when indicator count is loaded and there are no indicators', () => { - it('should render empty page when no indicators are found', async () => { - ( - useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount> - ).mockReturnValue({ - count: 0, - isLoading: false, - }); - ( - useIntegrationsPageLink as jest.MockedFunction<typeof useIntegrationsPageLink> - ).mockReturnValue(''); - ( - useTIDocumentationLink as jest.MockedFunction<typeof useTIDocumentationLink> - ).mockReturnValue(''); - - const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, { - wrapper: TestProvidersComponent, - }); - expect(asFragment()).toMatchSnapshot('no indicators'); + it('should render indicators table when we have some indicators', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount> + ).mockReturnValue({ + count: 7, + isLoading: false, + }); + (useIntegrations as jest.MockedFunction<typeof useIntegrations>).mockReturnValue({ + isLoading: false, + data: [], + } as unknown as UseQueryResult<Integration[]>); + + const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, { + wrapper: TestProvidersComponent, }); + expect(asFragment()).toMatchSnapshot(); }); - describe('when loading is done and we have some indicators', () => { - it('should render indicators table', async () => { - ( - useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount> - ).mockReturnValue({ - count: 7, - isLoading: false, - }); - - const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, { - wrapper: TestProvidersComponent, - }); - expect(asFragment()).toMatchSnapshot('indicators are present'); + it('should render indicators page when we have some ti integrations installed', async () => { + ( + useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount> + ).mockReturnValue({ + count: 0, + isLoading: false, + }); + (useIntegrations as jest.MockedFunction<typeof useIntegrations>).mockReturnValue({ + isLoading: false, + data: [ + { + categories: [THREAT_INTELLIGENCE_CATEGORY], + id: '123', + status: INSTALLATION_STATUS.Installed, + }, + ], + } as unknown as UseQueryResult<Integration[]>); + + const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, { + wrapper: TestProvidersComponent, }); + expect(asFragment()).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx index eb639d6c50f75..4c225e283b919 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx +++ b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx @@ -7,18 +7,24 @@ import { EuiLoadingLogo } from '@elastic/eui'; import React, { FC } from 'react'; +import { useIntegrations } from '../../hooks'; import { EmptyPage } from '../../modules/empty_page'; import { useIndicatorsTotalCount } from '../../modules/indicators'; import { SecuritySolutionPluginTemplateWrapper } from '../security_solution_plugin_template_wrapper'; /** - * Renders children only if TI integrations are enabled + * Renders the indicators page if the user has some Threat Intelligence integrations installed or + * the user is receiving indicators. + * If none are received, show the EmptyPage with a link to go install integrations. + * While the indicators call and the integrations call are loading, display a loading screen. */ export const IntegrationsGuard: FC = ({ children }) => { - const { count: indicatorsTotalCount, isLoading: isIndicatorsTotalCountLoading } = + const { isLoading: indicatorsTotalCountLoading, count: indicatorsTotalCount } = useIndicatorsTotalCount(); - if (isIndicatorsTotalCountLoading) { + const { isLoading: integrationLoading, data: installedTIIntegrations } = useIntegrations(); + + if (integrationLoading || indicatorsTotalCountLoading) { return ( <SecuritySolutionPluginTemplateWrapper isEmptyState> <EuiLoadingLogo logo="logoSecurity" size="xl" /> @@ -26,7 +32,7 @@ export const IntegrationsGuard: FC = ({ children }) => { ); } - const showEmptyPage = indicatorsTotalCount === 0; - - return showEmptyPage ? <EmptyPage /> : <>{children}</>; + // show indicators page if there are indicators, or if some ti integrations have been added + const showIndicatorsPage = indicatorsTotalCount > 0 || (installedTIIntegrations || []).length > 0; + return showIndicatorsPage ? <>{children}</> : <EmptyPage />; }; diff --git a/x-pack/plugins/threat_intelligence/public/containers/security_solution_plugin_template_wrapper.tsx b/x-pack/plugins/threat_intelligence/public/containers/security_solution_plugin_template_wrapper.tsx index e2e1a735b0c71..c4b97d1b7e722 100644 --- a/x-pack/plugins/threat_intelligence/public/containers/security_solution_plugin_template_wrapper.tsx +++ b/x-pack/plugins/threat_intelligence/public/containers/security_solution_plugin_template_wrapper.tsx @@ -8,7 +8,7 @@ import type { FC } from 'react'; import React from 'react'; import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template-types'; -import { useKibana } from '../hooks'; +import { useKibana } from '../hooks/use_kibana'; /** * Uses securityLayout service to retrieve shared plugin wrapper component and renders plugin routes / children inside of it. diff --git a/x-pack/plugins/threat_intelligence/public/hooks/index.ts b/x-pack/plugins/threat_intelligence/public/hooks/index.ts index 369a364e99ac9..29c019274e946 100644 --- a/x-pack/plugins/threat_intelligence/public/hooks/index.ts +++ b/x-pack/plugins/threat_intelligence/public/hooks/index.ts @@ -8,6 +8,7 @@ export * from './use_documentation_link'; export * from './use_field_types'; export * from './use_inspector'; +export * from './use_integrations'; export * from './use_integrations_page_link'; export * from './use_kibana'; export * from './use_security_context'; diff --git a/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.test.tsx b/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.test.tsx new file mode 100644 index 0000000000000..ee868cc9803d6 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.test.tsx @@ -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 React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'; +import { INSTALLATION_STATUS, THREAT_INTELLIGENCE_CATEGORY } from '../utils'; + +const createWrapper = () => { + const queryClient = new QueryClient(); + return ({ children }: { children: any }) => ( + <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> + ); +}; + +const renderUseQuery = (result: { items: any[] }) => + renderHook(() => useQuery(['integrations'], () => result), { + wrapper: createWrapper(), + }); + +describe('useIntegrations', () => { + it('should have undefined data during loading state', async () => { + const mockIntegrations = { items: [] }; + const { result, waitFor } = renderUseQuery(mockIntegrations); + + await waitFor(() => result.current.isLoading); + + expect(result.current.isLoading).toBeTruthy(); + expect(result.current.data).toBeUndefined(); + }); + + it('should return integrations on success', async () => { + const mockIntegrations = { + items: [ + { + categories: [THREAT_INTELLIGENCE_CATEGORY], + id: '123', + status: INSTALLATION_STATUS.Installed, + }, + ], + }; + const { result, waitFor } = renderUseQuery(mockIntegrations); + + await waitFor(() => result.current.isSuccess); + + expect(result.current.isLoading).toBeFalsy(); + expect(result.current.data).toEqual(mockIntegrations); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.ts b/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.ts new file mode 100644 index 0000000000000..a5d1f8f45bdcd --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/hooks/use_integrations.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 { useQuery, useQueryClient } from '@tanstack/react-query'; +import { filterIntegrations } from '../utils'; +import { useKibana } from './use_kibana'; + +type IntegrationInstallStatus = 'installed' | 'installing' | 'install_failed'; + +const INTEGRATIONS_URL = '/api/fleet/epm/packages'; + +const INTEGRATIONS_CALL_TIMEOUT = 2000; + +export interface IntegrationResponse { + items: Integration[]; +} + +export interface Integration { + categories: string[]; + id: string; + status: IntegrationInstallStatus; +} + +/** + * Retrieves integrations from the Fleet plugin endpoint /api/fleet/epm/packages. + * The integrations are then filtered, and we only keep the installed ones, + * with category threat_intel and excluding the ti_utils integration. + * We cancel the query in case it's taking too long to not block the Indicators page for the user. + */ +export const useIntegrations = () => { + const { http } = useKibana().services; + const queryKey = ['integrations']; + + // retrieving the list of integrations from the fleet plugin's endpoint + const fetchIntegrations = () => http.get<IntegrationResponse>(INTEGRATIONS_URL); + + const query = useQuery(queryKey, fetchIntegrations, { + select: (data: IntegrationResponse) => (data ? filterIntegrations(data.items) : []), + }); + + const queryClient = useQueryClient(); + + // cancel slow integrations call to unblock the UI + setTimeout(() => queryClient.cancelQueries(queryKey), INTEGRATIONS_CALL_TIMEOUT); + + return query; +}; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.tsx index 1e1f713957fb8..f161b7ac1d85c 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.tsx @@ -6,32 +6,25 @@ */ import React, { FC, VFC } from 'react'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { IndicatorsBarChartWrapper } from '../components/barchart'; import { IndicatorsTable } from '../components/table'; -import { useIndicators } from '../hooks/use_indicators'; +import { useAggregatedIndicators, useIndicators, useSourcererDataView } from '../hooks'; import { DefaultPageLayout } from '../../../components/layout'; import { useFilters } from '../../query_bar'; import { FiltersGlobal } from '../../../containers/filters_global'; -import { useSourcererDataView } from '../hooks/use_sourcerer_data_view'; import { FieldTypesProvider } from '../../../containers/field_types_provider'; import { InspectorProvider } from '../../../containers/inspector'; import { useColumnSettings } from '../components/table/hooks'; -import { useAggregatedIndicators } from '../hooks/use_aggregated_indicators'; import { IndicatorsFilters } from '../containers/filters'; -import { useSecurityContext } from '../../../hooks/use_security_context'; +import { useSecurityContext } from '../../../hooks'; import { UpdateStatus } from '../../../components/update_status'; -const queryClient = new QueryClient(); - const IndicatorsPageProviders: FC = ({ children }) => ( - <QueryClientProvider client={queryClient}> - <IndicatorsFilters> - <FieldTypesProvider> - <InspectorProvider>{children}</InspectorProvider> - </FieldTypesProvider> - </IndicatorsFilters> - </QueryClientProvider> + <IndicatorsFilters> + <FieldTypesProvider> + <InspectorProvider>{children}</InspectorProvider> + </FieldTypesProvider> + </IndicatorsFilters> ); const IndicatorsPageContent: VFC = () => { @@ -115,7 +108,3 @@ export const IndicatorsPage: VFC = () => ( <IndicatorsPageContent /> </IndicatorsPageProviders> ); - -// Note: This is for lazy loading -// eslint-disable-next-line import/no-default-export -export default IndicatorsPage; diff --git a/x-pack/plugins/threat_intelligence/public/plugin.tsx b/x-pack/plugins/threat_intelligence/public/plugin.tsx index 88214b3abe3f1..d1e4391edfc0a 100755 --- a/x-pack/plugins/threat_intelligence/public/plugin.tsx +++ b/x-pack/plugins/threat_intelligence/public/plugin.tsx @@ -8,9 +8,9 @@ import { CoreStart, Plugin } from '@kbn/core/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { Provider as ReduxStoreProvider } from 'react-redux'; -import React, { Suspense, VFC } from 'react'; +import React from 'react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; -import { KibanaContextProvider } from './hooks'; +import { KibanaContextProvider } from './hooks/use_kibana'; import { SecuritySolutionPluginContext, Services, @@ -20,22 +20,12 @@ import { } from './types'; import { SecuritySolutionContext } from './containers/security_solution_context'; import { EnterpriseGuard } from './containers/enterprise_guard'; -import { SecuritySolutionPluginTemplateWrapper } from './containers/security_solution_plugin_template_wrapper'; -import { IntegrationsGuard } from './containers/integrations_guard'; interface AppProps { securitySolutionContext: SecuritySolutionPluginContext; } -const LazyIndicatorsPage = React.lazy(() => import('./modules/indicators/pages/indicators')); - -const IndicatorsPage: VFC = () => ( - <SecuritySolutionPluginTemplateWrapper> - <Suspense fallback={<div />}> - <LazyIndicatorsPage /> - </Suspense> - </SecuritySolutionPluginTemplateWrapper> -); +const LazyIndicatorsPageWrapper = React.lazy(() => import('./containers/indicators_page_wrapper')); /** * This is used here: @@ -51,9 +41,7 @@ export const createApp = <SecuritySolutionContext.Provider value={securitySolutionContext}> <KibanaContextProvider services={services}> <EnterpriseGuard> - <IntegrationsGuard> - <IndicatorsPage /> - </IntegrationsGuard> + <LazyIndicatorsPageWrapper /> </EnterpriseGuard> </KibanaContextProvider> </SecuritySolutionContext.Provider> diff --git a/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.test.ts b/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.test.ts new file mode 100644 index 0000000000000..507bdb5ca4ab2 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Integration } from '../hooks'; +import { filterIntegrations, THREAT_INTELLIGENCE_CATEGORY, THREAT_INTELLIGENCE_UTILITIES } from '.'; + +describe('filterIntegrations', () => { + it('should empty array', async () => { + const mockIntegrations: Integration[] = []; + const result = filterIntegrations(mockIntegrations); + expect(result).toEqual(mockIntegrations); + }); + + it('should return only installed ti integrations (excluding ti_utils)', async () => { + const tiInstalledIntegration: Integration = { + categories: [THREAT_INTELLIGENCE_CATEGORY], + id: '123', + status: 'installed', + }; + const tiNotInstalledIntegration: Integration = { + categories: [THREAT_INTELLIGENCE_CATEGORY], + id: '456', + status: 'install_failed', + }; + const nonTIInstalledIntegration: Integration = { + categories: ['abc'], + id: '789', + status: 'installed', + }; + const tiUtilsIntegration: Integration = { + categories: [THREAT_INTELLIGENCE_CATEGORY], + id: THREAT_INTELLIGENCE_UTILITIES, + status: 'installed', + }; + const randomIntegration: Integration = { + categories: ['abc'], + id: 'def', + status: 'installing', + }; + const mockIntegrations: Integration[] = [ + tiInstalledIntegration, + tiNotInstalledIntegration, + nonTIInstalledIntegration, + tiUtilsIntegration, + randomIntegration, + ]; + + const result = filterIntegrations(mockIntegrations); + + expect(result).toEqual([tiInstalledIntegration]); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.ts b/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.ts new file mode 100644 index 0000000000000..075c34344cbc9 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/utils/filter_integrations.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 { Integration } from '../hooks'; + +export const INSTALLATION_STATUS = { + Installed: 'installed', + Installing: 'installing', + InstallFailed: 'install_failed', + NotInstalled: 'not_installed', +}; + +export const THREAT_INTELLIGENCE_CATEGORY = 'threat_intel'; + +export const THREAT_INTELLIGENCE_UTILITIES = 'ti_util'; + +/** + * Filter an array of integrations: + * - of status `installed` + * - with `threat_intel` category + * - excluding `ti_util` integration + * + * For more details see https://github.com/elastic/security-team/issues/4374 + * + * @param integrations the response from the packages endpoint in the Fleet plugin + */ +export const filterIntegrations = (integrations: Integration[]): Integration[] => + integrations.filter( + (pkg: any) => + pkg.status === INSTALLATION_STATUS.Installed && + pkg.categories.find((category: string) => category === THREAT_INTELLIGENCE_CATEGORY) != + null && + pkg.id !== THREAT_INTELLIGENCE_UTILITIES + ); diff --git a/x-pack/plugins/threat_intelligence/public/utils/index.ts b/x-pack/plugins/threat_intelligence/public/utils/index.ts new file mode 100644 index 0000000000000..6459678cb8073 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/utils/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 * from './filter_integrations'; diff --git a/x-pack/plugins/timelines/public/store/t_grid/model.ts b/x-pack/plugins/timelines/public/store/t_grid/model.ts index 42d22f74ea175..a2334db776a59 100644 --- a/x-pack/plugins/timelines/public/store/t_grid/model.ts +++ b/x-pack/plugins/timelines/public/store/t_grid/model.ts @@ -26,7 +26,7 @@ export interface TGridModelSettings { showCheckboxes: boolean; /** Specifies which column the timeline is sorted on, and the direction (ascending / descending) */ sort: SortColumnTable[]; - title: string; + title?: string; unit?: (n: number) => string | React.ReactNode; } export interface TGridModel extends TGridModelSettings { @@ -85,5 +85,6 @@ export type SubsetTGridModel = Readonly< | 'graphEventId' | 'sessionViewConfig' | 'queryFields' + | 'title' > >; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index d99e56245c44b..3ebe3a63523d2 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -372,6 +372,7 @@ "controls.controlGroup.management.validate.title": "Valider les sélections utilisateur", "controls.controlGroup.title": "Groupe de contrôle", "controls.controlGroup.toolbarButtonTitle": "Contrôles", + "controls.frame.error.message": "Une erreur s'est produite. En savoir plus", "controls.optionsList.description": "Ajoutez un menu pour la sélection de valeurs de champ.", "controls.optionsList.displayName": "Liste des options", "controls.optionsList.editor.allowMultiselectTitle": "Permettre des sélections multiples dans une liste déroulante", @@ -1162,7 +1163,6 @@ "data.advancedSettings.timepicker.refreshIntervalDefaultsText": "L'intervalle d'actualisation par défaut du filtre temporel. La valeur doit être spécifiée en millisecondes.", "data.advancedSettings.timepicker.refreshIntervalDefaultsTitle": "Intervalle d'actualisation du filtre temporel", "data.advancedSettings.timepicker.thisWeek": "Cette semaine", - "data.advancedSettings.timepicker.timeDefaultsText": "L’option de filtre temporel à utiliser lorsque Kibana est démarré sans filtre", "data.advancedSettings.timepicker.timeDefaultsTitle": "Filtre temporel par défaut", "data.advancedSettings.timepicker.today": "Aujourd'hui", "data.errors.fetchError": "Vérifiez votre réseau et la configuration de votre proxy. Si le problème persiste, contactez votre administrateur réseau.", @@ -2331,7 +2331,6 @@ "embeddableApi.errors.paneldoesNotExist": "Panneau introuvable", "embeddableApi.helloworld.displayName": "bonjour", "embeddableApi.panel.dashboardPanelAriaLabel": "Panneau du tableau de bord", - "embeddableApi.panel.errorEmbeddable.message": "Une erreur s'est produite. En savoir plus", "embeddableApi.panel.inspectPanel.displayName": "Inspecter", "embeddableApi.panel.inspectPanel.untitledEmbeddableFilename": "sans titre", "embeddableApi.panel.optionsMenu.panelOptionsButtonAriaLabel": "Options de panneau", @@ -6861,7 +6860,6 @@ "xpack.apm.tutorial.config_otel.description3": "La liste exhaustive des variables d'environnement, les paramètres de ligne de commande et les extraits de code de configuration (conformes à la spécification OpenTelemetry) se trouvent dans le {otelInstrumentationGuide}. Certains clients OpenTelemetry instables peuvent ne pas prendre en charge toutes les fonctionnalités et nécessitent peut-être d'autres mécanismes de configuration.", "xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment": "Définir l'URL personnalisée du serveur APM (par défaut : {defaultApmServerUrl})", "xpack.apm.tutorial.djangoClient.configure.textPost": "Consultez la [documentation]({documentationLink}) pour une utilisation avancée.", - "xpack.apm.tutorial.dotNetClient.configureAgent.textPost": "Si vous ne transférez pas une instance \"IConfiguration\" à l'agent (par ex., pour les applications non ASP.NET Core) vous pouvez également configurer l'agent par le biais de variables d'environnement. \n Consultez [the documentation]({documentationLink}) pour une utilisation avancée.", "xpack.apm.tutorial.dotNetClient.download.textPre": "Ajoutez le(s) package(s) d'agent depuis [NuGet]({allNuGetPackagesLink}) à votre application .NET. Plusieurs packages NuGet sont disponibles pour différents cas d'utilisation. \n\nPour une application ASP.NET Core avec Entity Framework Core, téléchargez le package [Elastic.Apm.NetCoreAll]({netCoreAllApmPackageLink}). Ce package ajoutera automatiquement chaque composant d'agent à votre application. \n\n Si vous souhaitez minimiser les dépendances, vous pouvez utiliser le package [Elastic.Apm.AspNetCore]({aspNetCorePackageLink}) uniquement pour le monitoring d'ASP.NET Core ou le package [Elastic.Apm.EfCore]({efCorePackageLink}) uniquement pour le monitoring d'Entity Framework Core. \n\n Si vous souhaitez seulement utiliser l'API d'agent publique pour l'instrumentation manuelle, utilisez le package [Elastic.Apm]({elasticApmPackageLink}).", "xpack.apm.tutorial.downloadServerRpm": "Vous cherchez les packages 32 bits ? Consultez la [Download page]({downloadPageLink}).", "xpack.apm.tutorial.downloadServerTitle": "Vous cherchez les packages 32 bits ? Consultez la [Download page]({downloadPageLink}).", @@ -15597,6 +15595,15 @@ "xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "Voir les instructions de configuration", "xpack.infra.homePage.settingsTabTitle": "Paramètres", "xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "Rechercher des données d'infrastructure… (par exemple host.name:host-1)", + "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "Total de la mémoire (moy.)", + "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "Utilisation de la mémoire (moy.)", + "xpack.infra.hostsTable.averageRxColumnHeader": "", + "xpack.infra.hostsTable.averageTxColumnHeader": "", + "xpack.infra.hostsTable.diskLatencyColumnHeader": "", + "xpack.infra.hostsTable.nameColumnHeader": "Nom", + "xpack.infra.hostsTable.numberOfCpusColumnHeader": "Nombre de processeurs", + "xpack.infra.hostsTable.operatingSystemColumnHeader": "Système d'exploitation", + "xpack.infra.hostsTable.servicesOnHostColumnHeader": "", "xpack.infra.infra.nodeDetails.apmTabLabel": "APM", "xpack.infra.infra.nodeDetails.createAlertLink": "Créer une règle d'inventaire", "xpack.infra.infra.nodeDetails.openAsPage": "Ouvrir en tant que page", @@ -17494,7 +17501,6 @@ "xpack.lens.formula.editorHelpInlineHideLabel": "Masquer la référence des fonctions", "xpack.lens.formula.editorHelpInlineHideToolTip": "Masquer la référence des fonctions", "xpack.lens.formula.editorHelpInlineShowToolTip": "Afficher la référence des fonctions", - "xpack.lens.formula.editorHelpOverlayToolTip": "Référence des fonctions", "xpack.lens.formula.fullScreenEnterLabel": "Développer", "xpack.lens.formula.fullScreenExitLabel": "Réduire", "xpack.lens.formula.kqlExtraArguments": "[kql]?: string, [lucene]?: string", @@ -17513,7 +17519,6 @@ "xpack.lens.formulaDocumentation.elasticsearchSection": "Elasticsearch", "xpack.lens.formulaDocumentation.elasticsearchSectionDescription": "Ces fonctions seront exécutées sur les documents bruts pour chaque ligne du tableau résultant, en agrégeant tous les documents correspondant aux dimensions de répartition en une seule valeur.", "xpack.lens.formulaDocumentation.filterRatio": "Rapport de filtre", - "xpack.lens.formulaDocumentation.header": "Référence de formule", "xpack.lens.formulaDocumentation.mathSection": "Mathématique", "xpack.lens.formulaDocumentation.mathSectionDescription": "Ces fonctions seront exécutées pour chaque ligne du tableau résultant en utilisant des valeurs uniques de la même ligne calculées à l'aide d'autres fonctions.", "xpack.lens.formulaDocumentation.percentOfTotal": "Pourcentage du total", @@ -17523,7 +17528,6 @@ "xpack.lens.formulaExampleMarkdown": "Exemples", "xpack.lens.formulaFrequentlyUsedHeading": "Formules courantes", "xpack.lens.formulaPlaceholderText": "Saisissez une formule en combinant des fonctions avec la fonction mathématique, telle que :", - "xpack.lens.formulaSearchPlaceholder": "Rechercher des fonctions", "xpack.lens.functions.collapse.args.byHelpText": "Colonnes selon lesquelles effectuer le regroupement - ces colonnes sont conservées telles quelles", "xpack.lens.functions.collapse.args.fnHelpText": "Fonction agrégée à appliquer", "xpack.lens.functions.collapse.args.metricHelpText": "Colonne pour laquelle calculer la fonction agrégée spécifiée", @@ -19170,7 +19174,6 @@ "xpack.ml.dataframe.analyticsList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage": "Une erreur s'est produite lors de la vérification de la possibilité pour un utilisateur de supprimer {destinationIndex} : {error}", "xpack.ml.dataframe.analyticsList.fetchSourceDataViewForCloneErrorMessage": "Une erreur s’est produite lors de la vérification de l’existence de la vue de données {dataView} : {error}", "xpack.ml.dataframe.analyticsList.forceStopModalBody": "{analyticsId} est en état d'échec. Vous devez arrêter la tâche et corriger la défaillance.", - "xpack.ml.dataframe.analyticsList.noSourceDataViewForClone": "Impossible de cloner la tâche d'analyse. Il n’existe aucune vue de données pour l’index {dataView}.", "xpack.ml.dataframe.analyticsList.progressOfPhase": "Progression de la phase {currentPhase} : {progress}%", "xpack.ml.dataframe.analyticsList.rowCollapse": "Masquer les détails pour {analyticsId}", "xpack.ml.dataframe.analyticsList.rowExpand": "Afficher les détails pour {analyticsId}", @@ -19622,7 +19625,6 @@ "xpack.ml.anomaliesTable.anomalyDetails.initialRecordScoreTooltip": "Score normalisé compris entre 0 et 100, qui indique l'importance relative de l'enregistrement des anomalies lorsque le groupe a été initialement traité.", "xpack.ml.anomaliesTable.anomalyDetails.interimResultLabel": "Résultat temporaire", "xpack.ml.anomaliesTable.anomalyDetails.jobIdTitle": "ID tâche", - "xpack.ml.anomaliesTable.anomalyDetails.multiBucketImpactTitle": "Impact multi-groupe", "xpack.ml.anomaliesTable.anomalyDetails.probabilityTitle": "Probabilité", "xpack.ml.anomaliesTable.anomalyDetails.recordScoreTitle": "Score d'enregistrement", "xpack.ml.anomaliesTable.anomalyDetails.recordScoreTooltip": "Score normalisé compris entre 0 et 100, qui indique l'importance relative du résultat d'enregistrement des anomalies. Cette valeur peut changer au fil de l'analyse de nouvelles données.", @@ -25604,7 +25606,7 @@ "xpack.securitySolution.eventFilters.showingTotal": "Affichage de {total} {total, plural, one {filtre d'événement} other {filtres d'événement}}", "xpack.securitySolution.eventsTab.unit": "{totalCount, plural, =1 {alerte externe} other {alertes externes}}", "xpack.securitySolution.eventsViewer.unit": "{totalCount, plural, =1 {événement} other {événements}}", - "xpack.securitySolution.exceptions.dissasociateListSuccessText": "La liste d'exceptions ({id}) a été retirée avec succès", + "xpack.securitySolution.exceptions.disassociateListSuccessText": "La liste d'exceptions ({id}) a été retirée avec succès", "xpack.securitySolution.exceptions.failedLoadPolicies": "Une erreur s'est produite lors du chargement des politiques : \"{error}\"", "xpack.securitySolution.exceptions.fetch404Error": "La liste d'exceptions associée ({listId}) n'existe plus. Veuillez retirer la liste d'exceptions manquante pour ajouter des exceptions supplémentaires à la règle de détection.", "xpack.securitySolution.exceptions.hideCommentsLabel": "Masquer ({comments}) {comments, plural, =1 {commentaire} other {commentaires}}", @@ -26150,8 +26152,6 @@ "xpack.securitySolution.components.mlPopover.jobsTable.filters.showAllJobsLabel": "Tâches Elastic", "xpack.securitySolution.components.mlPopover.jobsTable.filters.showSiemJobsLabel": "Tâches personnalisées", "xpack.securitySolution.components.mlPopup.cloudLink": "déploiement sur le cloud", - "xpack.securitySolution.components.mlPopup.errors.createJobFailureTitle": "Échec de création de la tâche", - "xpack.securitySolution.components.mlPopup.errors.startJobFailureTitle": "Échec de démarrage de la tâche", "xpack.securitySolution.components.mlPopup.hooks.errors.indexPatternFetchFailureTitle": "Échec de récupération du modèle d'indexation", "xpack.securitySolution.components.mlPopup.hooks.errors.siemJobFetchFailureTitle": "Échec de récupération de la tâche Security", "xpack.securitySolution.components.mlPopup.jobsTable.createCustomJobButtonLabel": "Création d'une tâche personnalisée", @@ -26212,7 +26212,6 @@ "xpack.securitySolution.containers.detectionEngine.createPrePackagedTimelineSuccesDescription": "Installation effectuée des modèles de chronologies prépackagées à partir d'Elastic", "xpack.securitySolution.containers.detectionEngine.rulesAndTimelines": "Impossible de récupérer les règles et les chronologies", "xpack.securitySolution.containers.detectionEngine.tagFetchFailDescription": "Impossible de récupérer les balises", - "xpack.securitySolution.containers.errors.stopJobFailureTitle": "Échec d'arrêt de la tâche", "xpack.securitySolution.contextMenuItemByRouter.viewDetails": "Afficher les détails", "xpack.securitySolution.customizeEventRenderers.customizeEventRenderersDescription": "Les outils de rendu d'événement transmettent automatiquement les détails les plus pertinents d'un événement pour révéler son histoire", "xpack.securitySolution.customizeEventRenderers.customizeEventRenderersTitle": "Personnaliser les outils de rendu d'événement", @@ -27340,7 +27339,7 @@ "xpack.securitySolution.detectionEngine.rules.allRules.actions.deleteRuleDescription": "Supprimer la règle", "xpack.securitySolution.detectionEngine.rules.allRules.actions.duplicateRuleDescription": "Dupliquer la règle", "xpack.securitySolution.detectionEngine.rules.allRules.actions.editRuleSettingsDescription": "Modifier les paramètres de règles", - "xpack.securitySolution.detectionEngine.rules.allRules.actions.editRuleSettingsToolTip": "Vous ne disposez pas des privilèges d'actions Kibana", + "xpack.securitySolution.detectionEngine.rules.allRules.actions.lackOfKibanaActionsFeaturePrivileges": "Vous ne disposez pas des privilèges d'actions Kibana", "xpack.securitySolution.detectionEngine.rules.allRules.actions.exportRuleDescription": "Exporter la règle", "xpack.securitySolution.detectionEngine.rules.allRules.batchActions.deleteSelectedImmutableTitle": "La sélection contient des règles immuables qui ne peuvent pas être supprimées", "xpack.securitySolution.detectionEngine.rules.allRules.batchActionsTitle": "Actions groupées", @@ -28148,7 +28147,7 @@ "xpack.securitySolution.exceptions.cancelLabel": "Annuler", "xpack.securitySolution.exceptions.clearExceptionsLabel": "Retirer la liste d'exceptions", "xpack.securitySolution.exceptions.commentEventLabel": "a ajouté un commentaire", - "xpack.securitySolution.exceptions.dissasociateExceptionListError": "Impossible de retirer la liste d'exceptions", + "xpack.securitySolution.exceptions.disassociateExceptionListError": "Impossible de retirer la liste d'exceptions", "xpack.securitySolution.exceptions.errorLabel": "Erreur", "xpack.securitySolution.exceptions.fetchError": "Erreur lors de la récupération de la liste d'exceptions", "xpack.securitySolution.exceptions.modalErrorAccordionText": "Afficher les informations de référence de la règle :", @@ -33706,4 +33705,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "Présentation" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8475224e49c07..a74277d17862a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -372,6 +372,7 @@ "controls.controlGroup.management.validate.title": "ユーザー選択を検証", "controls.controlGroup.title": "コントロールグループ", "controls.controlGroup.toolbarButtonTitle": "コントロール", + "controls.frame.error.message": "エラーが発生しました。続きを読む", "controls.optionsList.description": "フィールド値を選択するメニューを追加", "controls.optionsList.displayName": "オプションリスト", "controls.optionsList.editor.allowMultiselectTitle": "ドロップダウンでの複数選択を許可", @@ -1160,7 +1161,6 @@ "data.advancedSettings.timepicker.refreshIntervalDefaultsText": "時間フィルターのデフォルト更新間隔「値」はミリ秒で指定する必要があります。", "data.advancedSettings.timepicker.refreshIntervalDefaultsTitle": "タイムピッカーの更新間隔", "data.advancedSettings.timepicker.thisWeek": "今週", - "data.advancedSettings.timepicker.timeDefaultsText": "時間フィルターが選択されずにKibanaが起動した際に使用される時間フィルターです", "data.advancedSettings.timepicker.timeDefaultsTitle": "デフォルトのタイムピッカー", "data.advancedSettings.timepicker.today": "今日", "data.errors.fetchError": "ネットワークとプロキシ構成を確認してください。問題が解決しない場合は、ネットワーク管理者に問い合わせてください。", @@ -2327,7 +2327,6 @@ "embeddableApi.errors.paneldoesNotExist": "パネルが見つかりません", "embeddableApi.helloworld.displayName": "こんにちは", "embeddableApi.panel.dashboardPanelAriaLabel": "ダッシュボードパネル", - "embeddableApi.panel.errorEmbeddable.message": "エラーが発生しました。続きを読む", "embeddableApi.panel.inspectPanel.displayName": "検査", "embeddableApi.panel.inspectPanel.untitledEmbeddableFilename": "無題", "embeddableApi.panel.optionsMenu.panelOptionsButtonAriaLabel": "パネルオプション", @@ -6849,7 +6848,6 @@ "xpack.apm.tutorial.config_otel.description3": "環境変数、コマンドラインパラメーター、構成コードスニペット(OpenTelemetry仕様に準拠)の網羅的な一覧は、{otelInstrumentationGuide}をご覧ください。一部の不安定なOpenTelemetryクライアントでは、一部の機能がサポートされておらず、別の構成メカニズムが必要になる場合があります。", "xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment": "カスタム APM Server URL(デフォルト:{defaultApmServerUrl})を設定します", "xpack.apm.tutorial.djangoClient.configure.textPost": "高度な用途に関しては [ドキュメンテーション]({documentationLink})をご覧ください。", - "xpack.apm.tutorial.dotNetClient.configureAgent.textPost": "エージェントに「IConfiguration」インスタンスが渡されていない場合、(例:非 ASP.NET Core アプリケーションの場合)、エージェントを環境変数で構成することもできます。\n 高度な用途に関しては [ドキュメンテーション]({documentationLink})をご覧ください。", "xpack.apm.tutorial.dotNetClient.download.textPre": "[NuGet]({allNuGetPackagesLink})から .NET アプリケーションにエージェントパッケージを追加してください。用途の異なる複数の NuGet パッケージがあります。\n\nEntity Framework Core の ASP.NET Core アプリケーションの場合は、[Elastic.Apm.NetCoreAll]({netCoreAllApmPackageLink})パッケージをダウンロードしてください。このパッケージは、自動的にすべてのエージェントコンポーネントをアプリケーションに追加します。\n\n 依存性を最低限に抑えたい場合、ASP.NET Coreの監視のみに[Elastic.Apm.AspNetCore]({aspNetCorePackageLink})パッケージ、またはEntity Framework Coreの監視のみに[Elastic.Apm.EfCore]({efCorePackageLink})パッケージを使用することができます。\n\n 手動インストルメンテーションのみにパブリック Agent API を使用する場合は、[Elastic.Apm]({elasticApmPackageLink})パッケージを使用してください。", "xpack.apm.tutorial.downloadServerRpm": "32 ビットパッケージをお探しですか?[ダウンロードページ]({downloadPageLink})をご覧ください。", "xpack.apm.tutorial.downloadServerTitle": "32 ビットパッケージをお探しですか?[ダウンロードページ]({downloadPageLink})をご覧ください。", @@ -15582,6 +15580,15 @@ "xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "セットアップの手順を表示", "xpack.infra.homePage.settingsTabTitle": "設定", "xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "インフラストラクチャデータを検索…(例:host.name:host-1)", + "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "メモリ合計 (平均) ", + "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "メモリー使用状況(平均)", + "xpack.infra.hostsTable.averageTxColumnHeader": "", + "xpack.infra.hostsTable.averageRxColumnHeader": "", + "xpack.infra.hostsTable.diskLatencyColumnHeader": "", + "xpack.infra.hostsTable.nameColumnHeader": "名前", + "xpack.infra.hostsTable.numberOfCpusColumnHeader": "CPU数", + "xpack.infra.hostsTable.operatingSystemColumnHeader": "オペレーティングシステム", + "xpack.infra.hostsTable.servicesOnHostColumnHeader": "", "xpack.infra.infra.nodeDetails.apmTabLabel": "APM", "xpack.infra.infra.nodeDetails.createAlertLink": "インベントリルールの作成", "xpack.infra.infra.nodeDetails.openAsPage": "ページとして開く", @@ -17477,7 +17484,6 @@ "xpack.lens.formula.editorHelpInlineHideLabel": "関数リファレンスを非表示", "xpack.lens.formula.editorHelpInlineHideToolTip": "関数リファレンスを非表示", "xpack.lens.formula.editorHelpInlineShowToolTip": "関数リファレンスを表示", - "xpack.lens.formula.editorHelpOverlayToolTip": "機能リファレンス", "xpack.lens.formula.fullScreenEnterLabel": "拡張", "xpack.lens.formula.fullScreenExitLabel": "縮小", "xpack.lens.formula.kqlExtraArguments": "[kql]?:文字列、[lucene]?:文字列", @@ -17496,7 +17502,6 @@ "xpack.lens.formulaDocumentation.elasticsearchSection": "Elasticsearch", "xpack.lens.formulaDocumentation.elasticsearchSectionDescription": "これらの関数は結果テーブルの各行の未加工ドキュメントで実行され、内訳ディメンションと一致するすべてのドキュメントを単一の値に集約します。", "xpack.lens.formulaDocumentation.filterRatio": "フィルター比率", - "xpack.lens.formulaDocumentation.header": "式リファレンス", "xpack.lens.formulaDocumentation.mathSection": "数学処理", "xpack.lens.formulaDocumentation.mathSectionDescription": "これらの関数は、他の関数で計算された同じ行の単一の値を使用して、結果テーブルの各行で実行されます。", "xpack.lens.formulaDocumentation.percentOfTotal": "合計の割合", @@ -17506,7 +17511,6 @@ "xpack.lens.formulaExampleMarkdown": "例", "xpack.lens.formulaFrequentlyUsedHeading": "一般的な式", "xpack.lens.formulaPlaceholderText": "関数を演算と組み合わせて式を入力します。例:", - "xpack.lens.formulaSearchPlaceholder": "検索関数", "xpack.lens.functions.collapse.args.byHelpText": "グループ化の基準となる列。この列はそのまま保持されます", "xpack.lens.functions.collapse.args.fnHelpText": "適用する集計関数", "xpack.lens.functions.collapse.args.metricHelpText": "指定された集計関数を計算する列", @@ -19151,7 +19155,6 @@ "xpack.ml.dataframe.analyticsList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage": "ユーザーが{destinationIndex}を削除できるかどうかを確認するときにエラーが発生しました。{error}", "xpack.ml.dataframe.analyticsList.fetchSourceDataViewForCloneErrorMessage": "データビュー{dataView}が存在するかどうかを確認しているときにエラーが発生しました:{error}", "xpack.ml.dataframe.analyticsList.forceStopModalBody": "{analyticsId}は失敗状態です。ジョブを停止して、エラーを修正する必要があります。", - "xpack.ml.dataframe.analyticsList.noSourceDataViewForClone": "分析ジョブを複製できません。インデックス{dataView}のデータビューは存在しません。", "xpack.ml.dataframe.analyticsList.progressOfPhase": "フェーズ{currentPhase}の進捗:{progress}%", "xpack.ml.dataframe.analyticsList.rowCollapse": "{analyticsId}の詳細を非表示", "xpack.ml.dataframe.analyticsList.rowExpand": "{analyticsId}の詳細を表示", @@ -19603,7 +19606,6 @@ "xpack.ml.anomaliesTable.anomalyDetails.initialRecordScoreTooltip": "0~100の正規化されたスコア。バケットが最初に処理されたときの異常レコード結果の相対的な有意性を示します。", "xpack.ml.anomaliesTable.anomalyDetails.interimResultLabel": "中間結果", "xpack.ml.anomaliesTable.anomalyDetails.jobIdTitle": "ジョブID", - "xpack.ml.anomaliesTable.anomalyDetails.multiBucketImpactTitle": "複数バケットの影響", "xpack.ml.anomaliesTable.anomalyDetails.probabilityTitle": "確率", "xpack.ml.anomaliesTable.anomalyDetails.recordScoreTitle": "レコードスコア", "xpack.ml.anomaliesTable.anomalyDetails.recordScoreTooltip": "0~100の正規化されたスコア。異常レコード結果の相対的な有意性を示します。新しいデータが分析されると、この値が変化する場合があります。", @@ -25579,7 +25581,7 @@ "xpack.securitySolution.eventFilters.showingTotal": "{total} {total, plural, other {個のイベントフィルター}}を表示中", "xpack.securitySolution.eventsTab.unit": "外部{totalCount, plural, other {アラート}}", "xpack.securitySolution.eventsViewer.unit": "{totalCount, plural, other {イベント}}", - "xpack.securitySolution.exceptions.dissasociateListSuccessText": "例外リスト({id})が正常に削除されました", + "xpack.securitySolution.exceptions.disassociateListSuccessText": "例外リスト({id})が正常に削除されました", "xpack.securitySolution.exceptions.failedLoadPolicies": "ポリシーの読み込みエラーが発生しました:\"{error}\"", "xpack.securitySolution.exceptions.fetch404Error": "関連付けられた例外リスト({listId})は存在しません。その他の例外を検出ルールに追加するには、見つからない例外リストを削除してください。", "xpack.securitySolution.exceptions.hideCommentsLabel": "({comments}){comments, plural, other {件のコメント}}を非表示", @@ -26125,8 +26127,6 @@ "xpack.securitySolution.components.mlPopover.jobsTable.filters.showAllJobsLabel": "Elastic ジョブ", "xpack.securitySolution.components.mlPopover.jobsTable.filters.showSiemJobsLabel": "カスタムジョブ", "xpack.securitySolution.components.mlPopup.cloudLink": "クラウド展開", - "xpack.securitySolution.components.mlPopup.errors.createJobFailureTitle": "ジョブ作成エラー", - "xpack.securitySolution.components.mlPopup.errors.startJobFailureTitle": "ジョブ開始エラー", "xpack.securitySolution.components.mlPopup.hooks.errors.indexPatternFetchFailureTitle": "インデックスパターン取得エラー", "xpack.securitySolution.components.mlPopup.hooks.errors.siemJobFetchFailureTitle": "セキュリティジョブ取得エラー", "xpack.securitySolution.components.mlPopup.jobsTable.createCustomJobButtonLabel": "カスタムジョブを作成", @@ -26187,7 +26187,6 @@ "xpack.securitySolution.containers.detectionEngine.createPrePackagedTimelineSuccesDescription": "Elasticから事前にパッケージ化されているタイムラインテンプレートをインストールしました", "xpack.securitySolution.containers.detectionEngine.rulesAndTimelines": "ルールとタイムラインを取得できませんでした", "xpack.securitySolution.containers.detectionEngine.tagFetchFailDescription": "タグを取得できませんでした", - "xpack.securitySolution.containers.errors.stopJobFailureTitle": "ジョブ停止エラー", "xpack.securitySolution.contextMenuItemByRouter.viewDetails": "詳細を表示", "xpack.securitySolution.customizeEventRenderers.customizeEventRenderersDescription": "イベントレンダラーは、イベントで最も関連性が高い詳細情報を自動的に表示し、ストーリーを明らかにします", "xpack.securitySolution.customizeEventRenderers.customizeEventRenderersTitle": "イベントレンダラーのカスタマイズ", @@ -27315,7 +27314,7 @@ "xpack.securitySolution.detectionEngine.rules.allRules.actions.deleteRuleDescription": "ルールの削除", "xpack.securitySolution.detectionEngine.rules.allRules.actions.duplicateRuleDescription": "ルールの複製", "xpack.securitySolution.detectionEngine.rules.allRules.actions.editRuleSettingsDescription": "ルール設定の編集", - "xpack.securitySolution.detectionEngine.rules.allRules.actions.editRuleSettingsToolTip": "Kibana アクション特権がありません", + "xpack.securitySolution.detectionEngine.rules.allRules.actions.lackOfKibanaActionsFeaturePrivileges": "Kibana アクション特権がありません", "xpack.securitySolution.detectionEngine.rules.allRules.actions.exportRuleDescription": "ルールのエクスポート", "xpack.securitySolution.detectionEngine.rules.allRules.batchActions.deleteSelectedImmutableTitle": "選択には削除できないイミュータブルルールがあります", "xpack.securitySolution.detectionEngine.rules.allRules.batchActionsTitle": "一斉アクション", @@ -28123,7 +28122,7 @@ "xpack.securitySolution.exceptions.cancelLabel": "キャンセル", "xpack.securitySolution.exceptions.clearExceptionsLabel": "例外リストを削除", "xpack.securitySolution.exceptions.commentEventLabel": "コメントを追加しました", - "xpack.securitySolution.exceptions.dissasociateExceptionListError": "例外リストを削除できませんでした", + "xpack.securitySolution.exceptions.disassociateExceptionListError": "例外リストを削除できませんでした", "xpack.securitySolution.exceptions.errorLabel": "エラー", "xpack.securitySolution.exceptions.fetchError": "例外リストの取得エラー", "xpack.securitySolution.exceptions.modalErrorAccordionText": "ルール参照情報を表示:", @@ -33680,4 +33679,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "実地検証" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f212c72f2ff7b..6127534e13b93 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -372,6 +372,7 @@ "controls.controlGroup.management.validate.title": "验证用户选择", "controls.controlGroup.title": "控件组", "controls.controlGroup.toolbarButtonTitle": "控件", + "controls.frame.error.message": "发生错误。阅读更多内容", "controls.optionsList.description": "添加用于选择字段值的菜单。", "controls.optionsList.displayName": "选项列表", "controls.optionsList.editor.allowMultiselectTitle": "下拉列表中允许多选", @@ -1162,7 +1163,6 @@ "data.advancedSettings.timepicker.refreshIntervalDefaultsText": "时间筛选的默认刷新时间间隔。需要使用毫秒单位指定“值”。", "data.advancedSettings.timepicker.refreshIntervalDefaultsTitle": "时间筛选刷新时间间隔", "data.advancedSettings.timepicker.thisWeek": "本周", - "data.advancedSettings.timepicker.timeDefaultsText": "在未使用时间筛选的情况下启动 Kibana 时要使用的时间筛选选项", "data.advancedSettings.timepicker.timeDefaultsTitle": "时间筛选默认值", "data.advancedSettings.timepicker.today": "今日", "data.errors.fetchError": "请检查您的网络和代理配置。如果问题持续存在,请联系网络管理员。", @@ -2331,7 +2331,6 @@ "embeddableApi.errors.paneldoesNotExist": "未找到面板", "embeddableApi.helloworld.displayName": "hello world", "embeddableApi.panel.dashboardPanelAriaLabel": "仪表板面板", - "embeddableApi.panel.errorEmbeddable.message": "发生错误。阅读更多内容", "embeddableApi.panel.inspectPanel.displayName": "检查", "embeddableApi.panel.inspectPanel.untitledEmbeddableFilename": "未命名", "embeddableApi.panel.optionsMenu.panelOptionsButtonAriaLabel": "面板选项", @@ -6865,7 +6864,6 @@ "xpack.apm.tutorial.config_otel.description3": "{otelInstrumentationGuide}中提供了环境变量、命令行参数和配置代码片段(根据 OpenTelemetry 规范)的详细列表。某些不稳定的 OpenTelemetry 客户端可能不支持所有功能,并可能需要备选配置机制。", "xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment": "设置定制 APM Server URL(默认值:{defaultApmServerUrl})", "xpack.apm.tutorial.djangoClient.configure.textPost": "有关高级用法,请参阅[文档]({documentationLink})。", - "xpack.apm.tutorial.dotNetClient.configureAgent.textPost": "如果您未将 `IConfiguration` 实例传递给代理(例如非 ASP.NET Core 应用程序), 您还可以通过环境变量配置代理。\n 有关高级用法,请参阅[文档]({documentationLink})。", "xpack.apm.tutorial.dotNetClient.download.textPre": "将来自 [NuGet]({allNuGetPackagesLink}) 的代理软件包添加到 .NET 应用程序。有多个 NuGet 软件包可用于不同的用例。\n\n对于具有 Entity Framework Core 的 ASP.NET Core 应用程序,请下载 [Elastic.Apm.NetCoreAll]({netCoreAllApmPackageLink}) 软件包。此软件包将自动将每个 代理组件添加到您的应用程序。\n\n 如果您希望最大程度减少依存关系,您可以将 [Elastic.Apm.AspNetCore]({aspNetCorePackageLink}) 软件包仅用于 ASP.NET Core 监测,或将 [Elastic.Apm.EfCore]({efCorePackageLink}) 软件包仅用于 Entity Framework Core 监测。\n\n 如果 仅希望将公共代理 API 用于手动检测,请使用 [Elastic.Apm]({elasticApmPackageLink}) 软件包。", "xpack.apm.tutorial.downloadServerRpm": "寻找 32 位软件包?请参阅[下载页面]({downloadPageLink})。", "xpack.apm.tutorial.downloadServerTitle": "寻找 32 位软件包?请参阅[下载页面]({downloadPageLink})。", @@ -15603,6 +15601,15 @@ "xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "查看设置说明", "xpack.infra.homePage.settingsTabTitle": "设置", "xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "搜索基础设施数据……(例如 host.name:host-1)", + "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "内存合计 (平均值)", + "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "内存使用率(平均值)", + "xpack.infra.hostsTable.averageRxColumnHeader": "", + "xpack.infra.hostsTable.averageTxColumnHeader": "", + "xpack.infra.hostsTable.diskLatencyColumnHeader": "", + "xpack.infra.hostsTable.nameColumnHeader": "名称", + "xpack.infra.hostsTable.numberOfCpusColumnHeader": "# 个 CPU", + "xpack.infra.hostsTable.operatingSystemColumnHeader": "操作系统", + "xpack.infra.hostsTable.servicesOnHostColumnHeader": "", "xpack.infra.infra.nodeDetails.apmTabLabel": "APM", "xpack.infra.infra.nodeDetails.createAlertLink": "创建库存规则", "xpack.infra.infra.nodeDetails.openAsPage": "以页面形式打开", @@ -17502,7 +17509,6 @@ "xpack.lens.formula.editorHelpInlineHideLabel": "隐藏函数引用", "xpack.lens.formula.editorHelpInlineHideToolTip": "隐藏函数引用", "xpack.lens.formula.editorHelpInlineShowToolTip": "显示函数引用", - "xpack.lens.formula.editorHelpOverlayToolTip": "函数引用", "xpack.lens.formula.fullScreenEnterLabel": "展开", "xpack.lens.formula.fullScreenExitLabel": "折叠", "xpack.lens.formula.kqlExtraArguments": "[kql]?: string, [lucene]?: string", @@ -17521,7 +17527,6 @@ "xpack.lens.formulaDocumentation.elasticsearchSection": "Elasticsearch", "xpack.lens.formulaDocumentation.elasticsearchSectionDescription": "在原始文档上结果列表的每行都将执行这些函数,从而将匹配分解维度的所有文档聚合成单值。", "xpack.lens.formulaDocumentation.filterRatio": "筛选比", - "xpack.lens.formulaDocumentation.header": "公式参考", "xpack.lens.formulaDocumentation.mathSection": "数学", "xpack.lens.formulaDocumentation.mathSectionDescription": "结果表的每行使用相同行中使用其他函数计算的单值执行这些函数。", "xpack.lens.formulaDocumentation.percentOfTotal": "总计的百分比", @@ -17531,7 +17536,6 @@ "xpack.lens.formulaExampleMarkdown": "示例", "xpack.lens.formulaFrequentlyUsedHeading": "常用公式", "xpack.lens.formulaPlaceholderText": "通过将函数与数学表达式组合来键入公式,如:", - "xpack.lens.formulaSearchPlaceholder": "搜索函数", "xpack.lens.functions.collapse.args.byHelpText": "要作为分组依据的列 - 这些列将保持原样", "xpack.lens.functions.collapse.args.fnHelpText": "要应用的聚合函数", "xpack.lens.functions.collapse.args.metricHelpText": "用于计算以下项的指定聚合函数的列", @@ -19177,7 +19181,6 @@ "xpack.ml.dataframe.analyticsList.errorWithCheckingIfUserCanDeleteIndexNotificationErrorMessage": "检查用户是否能够删除 {destinationIndex} 时发生错误:{error}", "xpack.ml.dataframe.analyticsList.fetchSourceDataViewForCloneErrorMessage": "检查数据视图 {dataView} 是否存在时发生错误:{error}", "xpack.ml.dataframe.analyticsList.forceStopModalBody": "{analyticsId} 处于失败状态。您必须停止该作业并修复失败问题。", - "xpack.ml.dataframe.analyticsList.noSourceDataViewForClone": "无法克隆分析作业。对于索引 {dataView},不存在数据视图。", "xpack.ml.dataframe.analyticsList.progressOfPhase": "阶段 {currentPhase} 的进度:{progress}%", "xpack.ml.dataframe.analyticsList.rowCollapse": "隐藏 {analyticsId} 的详情", "xpack.ml.dataframe.analyticsList.rowExpand": "显示 {analyticsId} 的详情", @@ -19633,7 +19636,6 @@ "xpack.ml.anomaliesTable.anomalyDetails.initialRecordScoreTooltip": "介于 0-100 之间的标准化分数,表示初始处理存储桶时异常记录的相对意义。", "xpack.ml.anomaliesTable.anomalyDetails.interimResultLabel": "中间结果", "xpack.ml.anomaliesTable.anomalyDetails.jobIdTitle": "作业 ID", - "xpack.ml.anomaliesTable.anomalyDetails.multiBucketImpactTitle": "多存储桶影响", "xpack.ml.anomaliesTable.anomalyDetails.probabilityTitle": "可能性", "xpack.ml.anomaliesTable.anomalyDetails.recordScoreTitle": "记录分数", "xpack.ml.anomaliesTable.anomalyDetails.recordScoreTooltip": "介于 0-100 之间的标准化分数,表示异常记录结果的相对意义。在分析新数据时,该值可能会更改。", @@ -25613,7 +25615,7 @@ "xpack.securitySolution.eventFilters.showingTotal": "正在显示 {total} 个{total, plural, other {事件筛选}}", "xpack.securitySolution.eventsTab.unit": "个外部{totalCount, plural, other {告警}}", "xpack.securitySolution.eventsViewer.unit": "{totalCount, plural, other {个事件}}", - "xpack.securitySolution.exceptions.dissasociateListSuccessText": "例外列表 ({id}) 已成功移除", + "xpack.securitySolution.exceptions.disassociateListSuccessText": "例外列表 ({id}) 已成功移除", "xpack.securitySolution.exceptions.failedLoadPolicies": "加载策略时出错:“{error}”", "xpack.securitySolution.exceptions.fetch404Error": "关联的例外列表 ({listId}) 已不存在。请移除缺少的例外列表,以将其他例外添加到检测规则。", "xpack.securitySolution.exceptions.hideCommentsLabel": "隐藏 ({comments}) 个{comments, plural, other {注释}}", @@ -26159,8 +26161,6 @@ "xpack.securitySolution.components.mlPopover.jobsTable.filters.showAllJobsLabel": "Elastic 作业", "xpack.securitySolution.components.mlPopover.jobsTable.filters.showSiemJobsLabel": "定制作业", "xpack.securitySolution.components.mlPopup.cloudLink": "云部署", - "xpack.securitySolution.components.mlPopup.errors.createJobFailureTitle": "创建作业失败", - "xpack.securitySolution.components.mlPopup.errors.startJobFailureTitle": "启动作业失败", "xpack.securitySolution.components.mlPopup.hooks.errors.indexPatternFetchFailureTitle": "索引模式提取失败", "xpack.securitySolution.components.mlPopup.hooks.errors.siemJobFetchFailureTitle": "Security 作业提取失败", "xpack.securitySolution.components.mlPopup.jobsTable.createCustomJobButtonLabel": "创建定制作业", @@ -26221,7 +26221,6 @@ "xpack.securitySolution.containers.detectionEngine.createPrePackagedTimelineSuccesDescription": "安装 Elastic 预先打包的时间线模板", "xpack.securitySolution.containers.detectionEngine.rulesAndTimelines": "无法提取规则和时间线", "xpack.securitySolution.containers.detectionEngine.tagFetchFailDescription": "无法提取标签", - "xpack.securitySolution.containers.errors.stopJobFailureTitle": "停止作业失败", "xpack.securitySolution.contextMenuItemByRouter.viewDetails": "查看详情", "xpack.securitySolution.customizeEventRenderers.customizeEventRenderersDescription": "事件呈现器自动在事件中传送最相关的详情,以揭示其故事", "xpack.securitySolution.customizeEventRenderers.customizeEventRenderersTitle": "定制事件呈现器", @@ -27349,7 +27348,7 @@ "xpack.securitySolution.detectionEngine.rules.allRules.actions.deleteRuleDescription": "删除规则", "xpack.securitySolution.detectionEngine.rules.allRules.actions.duplicateRuleDescription": "复制规则", "xpack.securitySolution.detectionEngine.rules.allRules.actions.editRuleSettingsDescription": "编辑规则设置", - "xpack.securitySolution.detectionEngine.rules.allRules.actions.editRuleSettingsToolTip": "您没有 Kibana 操作权限", + "xpack.securitySolution.detectionEngine.rules.allRules.actions.lackOfKibanaActionsFeaturePrivileges": "您没有 Kibana 操作权限", "xpack.securitySolution.detectionEngine.rules.allRules.actions.exportRuleDescription": "导出规则", "xpack.securitySolution.detectionEngine.rules.allRules.batchActions.deleteSelectedImmutableTitle": "选择内容包含无法删除的不可变规则", "xpack.securitySolution.detectionEngine.rules.allRules.batchActionsTitle": "批处理操作", @@ -28157,7 +28156,7 @@ "xpack.securitySolution.exceptions.cancelLabel": "取消", "xpack.securitySolution.exceptions.clearExceptionsLabel": "移除例外列表", "xpack.securitySolution.exceptions.commentEventLabel": "已添加注释", - "xpack.securitySolution.exceptions.dissasociateExceptionListError": "无法移除例外列表", + "xpack.securitySolution.exceptions.disassociateExceptionListError": "无法移除例外列表", "xpack.securitySolution.exceptions.errorLabel": "错误", "xpack.securitySolution.exceptions.fetchError": "提取例外列表时出错", "xpack.securitySolution.exceptions.modalErrorAccordionText": "显示规则引用信息:", @@ -33717,4 +33716,4 @@ "xpack.painlessLab.title": "Painless 实验室", "xpack.painlessLab.walkthroughButtonLabel": "指导" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.test.ts index 70987db3f2d49..53b75afd774da 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.test.ts @@ -10,17 +10,28 @@ import { getFilter } from './get_filter'; describe('getFilter', () => { test('should return message filter', () => { expect(getFilter({ message: 'test message' })).toEqual([ - 'message: "test message" OR error.message: "test message"', + '(message: "test message" OR error.message: "test message")', ]); }); test('should return outcome filter', () => { expect(getFilter({ outcomeFilter: ['failure', 'warning', 'success', 'unknown'] })).toEqual([ - 'event.outcome: failure OR kibana.alerting.outcome: warning OR kibana.alerting.outcome:success OR (event.outcome: success AND NOT kibana.alerting.outcome:*) OR event.outcome: unknown', + '(event.outcome: failure OR kibana.alerting.outcome: warning OR kibana.alerting.outcome:success OR (event.outcome: success AND NOT kibana.alerting.outcome:*) OR event.outcome: unknown)', ]); }); test('should return runId filter', () => { expect(getFilter({ runId: 'test' })).toEqual(['kibana.alert.rule.execution.uuid: test']); }); + + test('should return filter for both message and outcome', () => { + expect(getFilter({ message: 'test message', outcomeFilter: ['failure', 'warning'] })).toEqual([ + '(message: "test message" OR error.message: "test message")', + '(event.outcome: failure OR kibana.alerting.outcome: warning)', + ]); + }); + + test('should not return filter if outcome filter is invalid', () => { + expect(getFilter({ outcomeFilter: ['doesntexist'] })).toEqual([]); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.ts index 92bbdc38ae4bb..59ccef9734b65 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/get_filter.ts @@ -19,11 +19,14 @@ export const getFilter = ({ if (message) { const escapedMessage = message.replace(/([\)\(\<\>\}\{\"\:\\])/gm, '\\$&'); - filter.push(`message: "${escapedMessage}" OR error.message: "${escapedMessage}"`); + filter.push(`(message: "${escapedMessage}" OR error.message: "${escapedMessage}")`); } if (outcomeFilter && outcomeFilter.length) { - filter.push(getOutcomeFilter(outcomeFilter)); + const outcomeFilterKQL = getOutcomeFilter(outcomeFilter); + if (outcomeFilterKQL) { + filter.push(`(${outcomeFilterKQL})`); + } } if (runId) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.test.ts index d06447be31fbc..56de4f5c4c890 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_action_error_log.test.ts @@ -117,7 +117,7 @@ describe('loadActionErrorLog', () => { "query": Object { "date_end": "2022-03-23T16:17:53.482Z", "date_start": "2022-03-23T16:17:53.482Z", - "filter": "message: \\"test\\" OR error.message: \\"test\\" and kibana.alert.rule.execution.uuid: 123", + "filter": "(message: \\"test\\" OR error.message: \\"test\\") and kibana.alert.rule.execution.uuid: 123", "page": 1, "per_page": 10, "sort": "[{\\"@timestamp\\":{\\"order\\":\\"asc\\"}}]", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_log_aggregations.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_log_aggregations.test.ts index c40f0a0b2735d..43655ff21e3bb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_log_aggregations.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/load_execution_log_aggregations.test.ts @@ -47,7 +47,8 @@ describe('loadExecutionLogAggregations', () => { id: 'test-id', dateStart: '2022-03-23T16:17:53.482Z', dateEnd: '2022-03-23T16:17:53.482Z', - outcomeFilter: ['success'], + outcomeFilter: ['success', 'warning'], + message: 'test-message', perPage: 10, page: 0, sort: [sortTimestamp], @@ -84,7 +85,7 @@ describe('loadExecutionLogAggregations', () => { "query": Object { "date_end": "2022-03-23T16:17:53.482Z", "date_start": "2022-03-23T16:17:53.482Z", - "filter": "kibana.alerting.outcome:success OR (event.outcome: success AND NOT kibana.alerting.outcome:*)", + "filter": "(message: \\"test-message\\" OR error.message: \\"test-message\\") and (kibana.alerting.outcome:success OR (event.outcome: success AND NOT kibana.alerting.outcome:*) OR kibana.alerting.outcome: warning)", "page": 1, "per_page": 10, "sort": "[{\\"timestamp\\":{\\"order\\":\\"asc\\"}}]", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx index 0112f2296ee05..f937663cd27a3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx @@ -181,7 +181,7 @@ describe('action_type_form', () => { // Verify that the tooltip renders // Use fake timers so we don't have to wait for the EuiToolTip timeout - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); wrapper.find('[data-test-subj="action-group-error-icon"]').first().simulate('mouseOver'); // Run the timers so the EuiTooltip will be visible jest.runAllTimers(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.test.tsx index 8a0b52b082ea9..1fafae26032c0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.test.tsx @@ -18,7 +18,7 @@ const defaultProps = { describe('FieldName', () => { beforeEach(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); test('it renders the field name', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx index dfebee8e5d6ff..58ca1fb0193cd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx @@ -232,7 +232,7 @@ describe('rule_edit', () => { it('should render an alert icon next to save button stating the potential change in permissions', async () => { // Use fake timers so we don't have to wait for the EuiToolTip timeout - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); await setup(); expect(wrapper.find('[data-test-subj="changeInPrivilegesTip"]').exists()).toBeTruthy(); @@ -241,7 +241,7 @@ describe('rule_edit', () => { }); // Run the timers so the EuiTooltip will be visible - jest.runAllTimers(); + jest.runOnlyPendingTimers(); wrapper.update(); expect(wrapper.find('.euiToolTipPopover').text()).toBe( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx index 6bfe953e28696..39613e456157d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx @@ -1106,7 +1106,7 @@ describe('rules_list component with items', () => { it('renders table of rules', async () => { // Use fake timers so we don't have to wait for the EuiToolTip timeout - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); await setup(); expect(wrapper.find('EuiBasicTable')).toHaveLength(1); expect(wrapper.find('EuiTableRow')).toHaveLength(mockedRulesData.length); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.test.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.test.tsx index f4b07c6f2f5ad..6de4d8095d267 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.test.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.test.tsx @@ -361,7 +361,7 @@ describe('ES deprecation logs', () => { describe('Poll for logs count', () => { beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // First request should make the step be complete httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({ diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts index 457c0c4ec2be5..3af99109510a8 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts @@ -103,9 +103,9 @@ describe('ES deprecations table', () => { (deprecation) => deprecation.isCritical === false ); - expect(find('criticalDeprecationsCount').text()).toContain(criticalDeprecations.length); + expect(find('criticalDeprecationsCount').text()).toContain(String(criticalDeprecations.length)); - expect(find('warningDeprecationsCount').text()).toContain(warningDeprecations.length); + expect(find('warningDeprecationsCount').text()).toContain(String(warningDeprecations.length)); }); describe('remote clusters callout', () => { diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts index 845141fb7784f..f181aef4f7c05 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts @@ -27,7 +27,7 @@ describe('Reindex deprecation flyout', () => { let testBed: ElasticsearchTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/time_manipulation.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/time_manipulation.ts index 65cec19549736..9789fa3a8f52e 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/time_manipulation.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/time_manipulation.ts @@ -8,7 +8,7 @@ import { act } from 'react-dom/test-utils'; /** - * These helpers are intended to be used in conjunction with jest.useFakeTimers(). + * These helpers are intended to be used in conjunction with jest.useFakeTimers('legacy'). */ const flushPromiseJobQueue = async () => { diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/deprecations_table/deprecations_table.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/deprecations_table/deprecations_table.test.ts index 7a62fa33bb876..e4c85d47bc464 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/deprecations_table/deprecations_table.test.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/deprecations_table/deprecations_table.test.ts @@ -69,10 +69,10 @@ describe('Kibana deprecations - Deprecations table', () => { const { find } = testBed; expect(find('criticalDeprecationsCount').text()).toContain( - mockedCriticalKibanaDeprecations.length + String(mockedCriticalKibanaDeprecations.length) ); expect(find('warningDeprecationsCount').text()).toContain( - mockedWarningKibanaDeprecations.length + String(mockedWarningKibanaDeprecations.length) ); }); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx index 688e060705ee4..bc93c05fb7e7a 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/backup_step/backup_step.test.tsx @@ -153,7 +153,7 @@ describe('Overview - Backup Step', () => { describe('poll for new status', () => { beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // First request will succeed. httpRequestsMockHelpers.setLoadCloudBackupStatusResponse({ diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/kibana_deprecation_issues.test.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/kibana_deprecation_issues.test.tsx index 77c8a9e998edf..cead46f258c4a 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/kibana_deprecation_issues.test.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/kibana_deprecation_issues.test.tsx @@ -57,8 +57,8 @@ describe('Overview - Fix deprecation issues step - Kibana deprecations', () => { const { exists, find } = testBed; expect(exists('kibanaStatsPanel')).toBe(true); - expect(find('kibanaStatsPanel.criticalDeprecations').text()).toContain(1); - expect(find('kibanaStatsPanel.warningDeprecations').text()).toContain(2); + expect(find('kibanaStatsPanel.criticalDeprecations').text()).toContain('1'); + expect(find('kibanaStatsPanel.warningDeprecations').text()).toContain('2'); }); test('panel links to Kibana deprecations page', () => { @@ -77,7 +77,7 @@ describe('Overview - Fix deprecation issues step - Kibana deprecations', () => { const { exists, find } = testBed; expect(exists('kibanaStatsPanel')).toBe(true); - expect(find('kibanaStatsPanel.criticalDeprecations').text()).toContain(1); + expect(find('kibanaStatsPanel.criticalDeprecations').text()).toContain('1'); expect(exists('kibanaStatsPanel.noWarningDeprecationIssues')).toBe(true); }); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/migrate_system_indices/step_completion.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/migrate_system_indices/step_completion.test.ts index cbece74355d6d..9c69b4bb18475 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/migrate_system_indices/step_completion.test.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/migrate_system_indices/step_completion.test.ts @@ -55,7 +55,7 @@ describe('Overview - Migrate system indices - Step completion', () => { describe('Poll for new status', () => { beforeEach(async () => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); // First request should make the step be incomplete httpRequestsMockHelpers.setLoadSystemIndicesMigrationStatus({ diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json_page.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json_page.test.ts index 54352e278770b..b7e80808cfb82 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json_page.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_json_page.test.ts @@ -21,7 +21,7 @@ describe('<JsonWatchEditPage /> create route', () => { let testBed: WatchCreateJsonTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold_page.test.tsx b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold_page.test.tsx index 4fe5fb3445da8..3395e8d95de7c 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold_page.test.tsx +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_create_threshold_page.test.tsx @@ -84,7 +84,7 @@ describe('<ThresholdWatchEditPage /> create route', () => { let testBed: WatchCreateThresholdTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_edit_page.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_edit_page.test.ts index f57dcb6788fe1..f392806b854ff 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_edit_page.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_edit_page.test.ts @@ -21,7 +21,7 @@ describe('<WatchEditPage />', () => { let testBed: WatchEditTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_list_page.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_list_page.test.ts index b856002775188..995b363504685 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_list_page.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_list_page.test.ts @@ -18,7 +18,7 @@ describe('<WatchListPage />', () => { let testBed: WatchListTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_status_page.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_status_page.test.ts index 1d00b6c699721..3ae7415630750 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_status_page.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_status_page.test.ts @@ -45,7 +45,7 @@ describe('<WatchStatusPage />', () => { let testBed: WatchStatusTestBed; beforeAll(() => { - jest.useFakeTimers(); + jest.useFakeTimers('legacy'); }); afterAll(() => { diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 090e48f7a8a2d..d2831b61799f5 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -197,6 +197,18 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) ])}`, `--xpack.actions.preconfiguredAlertHistoryEsIndex=${preconfiguredAlertHistoryEsIndex}`, `--xpack.actions.preconfigured=${JSON.stringify({ + 'my-test-email': { + actionTypeId: '.email', + name: 'TestEmail#xyz', + config: { + from: 'me@test.com', + service: '__json', + }, + secrets: { + user: 'user', + password: 'password', + }, + }, 'my-slack1': { actionTypeId: '.slack', name: 'Slack#xyz', diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts index ae874d942e75b..316d1916b4af2 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts @@ -7,10 +7,13 @@ import http from 'http'; import https from 'https'; -import { Plugin, CoreSetup, IRouter } from '@kbn/core/server'; +import { Plugin, CoreSetup } from '@kbn/core/server'; import { EncryptedSavedObjectsPluginStart } from '@kbn/encrypted-saved-objects-plugin/server'; import { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; -import { PluginSetupContract as ActionsPluginSetupContract } from '@kbn/actions-plugin/server/plugin'; +import { + PluginSetupContract as ActionsPluginSetupContract, + PluginStartContract as ActionsPluginStartContract, +} from '@kbn/actions-plugin/server/plugin'; import { ActionType } from '@kbn/actions-plugin/server'; import { initPlugin as initPagerduty } from './pagerduty_simulation'; import { initPlugin as initSwimlane } from './swimlane_simulation'; @@ -22,6 +25,7 @@ import { initPlugin as initSlack } from './slack_simulation'; import { initPlugin as initWebhook } from './webhook_simulation'; import { initPlugin as initMSExchange } from './ms_exchage_server_simulation'; import { initPlugin as initXmatters } from './xmatters_simulation'; +import { initPlugin as initUnsecuredAction } from './unsecured_actions_simulation'; export const NAME = 'actions-FTS-external-service-simulators'; @@ -82,8 +86,9 @@ interface FixtureSetupDeps { features: FeaturesPluginSetup; } -interface FixtureStartDeps { +export interface FixtureStartDeps { encryptedSavedObjects: EncryptedSavedObjectsPluginStart; + actions: ActionsPluginStartContract; } export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, FixtureStartDeps> { @@ -126,7 +131,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu }, }); - const router: IRouter = core.http.createRouter(); + const router = core.http.createRouter(); initXmatters(router, getExternalServiceSimulatorPath(ExternalServiceSimulator.XMATTERS)); initPagerduty(router, getExternalServiceSimulatorPath(ExternalServiceSimulator.PAGERDUTY)); @@ -137,8 +142,10 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu router, getExternalServiceSimulatorPath(ExternalServiceSimulator.SERVICENOW) ); + initUnsecuredAction(router, core); } public start() {} + public stop() {} } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/unsecured_actions_simulation.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/unsecured_actions_simulation.ts new file mode 100644 index 0000000000000..5900f698045a0 --- /dev/null +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/unsecured_actions_simulation.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { + CoreSetup, + RequestHandlerContext, + KibanaRequest, + KibanaResponseFactory, + IKibanaResponse, + IRouter, +} from '@kbn/core/server'; +import { FixtureStartDeps } from './plugin'; + +export function initPlugin(router: IRouter, coreSetup: CoreSetup<FixtureStartDeps>) { + router.post( + { + path: `/api/sample_unsecured_action`, + validate: { + body: schema.object({ + requesterId: schema.string(), + id: schema.string(), + params: schema.recordOf(schema.string(), schema.any()), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest<any, any, any, any>, + res: KibanaResponseFactory + ): Promise<IKibanaResponse<any>> { + const [_, { actions }] = await coreSetup.getStartServices(); + const { body } = req; + + try { + const unsecuredActionsClient = actions.getUnsecuredActionsClient(); + const { requesterId, id, params } = body; + await unsecuredActionsClient.bulkEnqueueExecution(requesterId, [{ id, params }]); + + return res.ok({ body: { status: 'success' } }); + } catch (err) { + return res.ok({ body: { status: 'error', error: `${err}` } }); + } + } + ); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_delete.ts new file mode 100644 index 0000000000000..91de3084993ff --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/bulk_delete.ts @@ -0,0 +1,563 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { UserAtSpaceScenarios, SuperuserAtSpace1 } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; + +const defaultSuccessfulResponse = { errors: [], total: 1, taskIdsFailedToBeDeleted: [] }; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const es = getService('es'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + describe('bulkDelete', () => { + const objectRemover = new ObjectRemover(supertest); + + after(() => objectRemover.removeAll()); + + const getScheduledTask = async (id: string) => { + return await es.get({ + id: `task:${id}`, + index: '.kibana_task_manager', + }); + }; + + for (const scenario of UserAtSpaceScenarios) { + const { user, space } = scenario; + + describe(scenario.id, () => { + afterEach(() => objectRemover.removeAll()); + it('should handle bulk delete of one rule appropriately based on id', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData()) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule1.id] }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'space_1_all_alerts_none_actions at space1': + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.statusCode).to.eql(200); + try { + await getScheduledTask(createdRule1.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle bulk delete of one rule appropriately based on id when consumer is the same as producer', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.restricted-noop', + consumer: 'alertsRestrictedFixture', + }) + ) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule1.id] }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: + 'Unauthorized to bulkDelete a "test.restricted-noop" rule for "alertsRestrictedFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'space_1_all at space1': + case 'space_1_all_alerts_none_actions at space1': + expect(response.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: 'No rules found for bulk delete', + }); + expect(response.statusCode).to.eql(400); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'superuser at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.statusCode).to.eql(200); + try { + await getScheduledTask(createdRule1.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle delete alert request appropriately when consumer is not the producer', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.restricted-noop', + consumer: 'alertsFixture', + }) + ) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule1.id] }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'space_1_all at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'space_1_all_with_restricted_fixture at space1': + case 'global_read at space1': + expect(response.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: 'No rules found for bulk delete', + }); + expect(response.statusCode).to.eql(400); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'superuser at space1': + expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.statusCode).to.eql(200); + try { + await getScheduledTask(createdRule1.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle delete alert request appropriately when consumer is "alerts"', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.noop', + consumer: 'alerts', + }) + ) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [createdRule1.id] }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkDelete a "test.noop" rule by "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + // Ensure task still exists + await getScheduledTask(createdRule1.scheduled_task_id); + break; + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.statusCode).to.eql(200); + try { + await getScheduledTask(createdRule1.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle bulk delete of several rules ids appropriately based on ids', async () => { + const rules = await Promise.all( + Array.from({ length: 3 }).map(() => + supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ tags: ['multiple-rules-edit'] })) + .expect(200) + ) + ); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: rules.map((rule) => rule.body.id) }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + await Promise.all( + rules.map((rule) => { + objectRemover.add(space.id, rule.body.id, 'rule', 'alerting'); + return getScheduledTask(rule.body.scheduled_task_id); + }) + ); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + await Promise.all( + rules.map((rule) => { + objectRemover.add(space.id, rule.body.id, 'rule', 'alerting'); + return getScheduledTask(rule.body.scheduled_task_id); + }) + ); + break; + case 'space_1_all_alerts_none_actions at space1': + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql({ ...defaultSuccessfulResponse, total: 3 }); + expect(response.statusCode).to.eql(200); + for (const rule of rules) { + try { + await getScheduledTask(rule.body.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should handle bulk delete of several rules ids appropriately based on filter', async () => { + const rules = await Promise.all( + Array.from({ length: 3 }).map(() => + supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ tags: ['multiple-rules-delete'] })) + .expect(200) + ) + ); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ filter: `alert.attributes.tags: "multiple-rules-delete"` }) + .auth(user.username, user.password); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + await Promise.all( + rules.map((rule) => { + objectRemover.add(space.id, rule.body.id, 'rule', 'alerting'); + return getScheduledTask(rule.body.scheduled_task_id); + }) + ); + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + await Promise.all( + rules.map((rule) => { + objectRemover.add(space.id, rule.body.id, 'rule', 'alerting'); + return getScheduledTask(rule.body.scheduled_task_id); + }) + ); + break; + case 'space_1_all_alerts_none_actions at space1': + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql({ ...defaultSuccessfulResponse, total: 3 }); + expect(response.statusCode).to.eql(200); + for (const rule of rules) { + try { + await getScheduledTask(rule.body.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + } + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + + it('should not delete rule from another space', async () => { + const { body: createdRule } = await supertest + .post(`${getUrlPrefix('other')}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData()) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix('other')}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password) + .send({ ids: [createdRule.id] }); + + switch (scenario.id) { + // This superuser has more privileges that we think + case 'superuser at space1': + expect(response.body).to.eql(defaultSuccessfulResponse); + expect(response.statusCode).to.eql(200); + try { + await getScheduledTask(createdRule.scheduled_task_id); + throw new Error('Should have removed scheduled task'); + } catch (e) { + expect(e.meta.statusCode).to.eql(404); + } + break; + case 'global_read at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + objectRemover.add('other', createdRule.id, 'rule', 'alerting'); + await getScheduledTask(createdRule.scheduled_task_id); + break; + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'space_1_all at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(response.body).to.eql({ + error: 'Forbidden', + message: 'Unauthorized to find rules for any rule types', + statusCode: 403, + }); + expect(response.statusCode).to.eql(403); + expect(response.statusCode).to.eql(403); + objectRemover.add('other', createdRule.id, 'rule', 'alerting'); + await getScheduledTask(createdRule.scheduled_task_id); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + }); + } + + describe('Validation tests', () => { + const { user, space } = SuperuserAtSpace1; + it('should throw an error when bulk delete of rules when both ids and filter supplied in payload', async () => { + const { body: createdRule1 } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData({ tags: ['foo'] })) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ filter: 'fake_filter', ids: [createdRule1.id] }) + .auth(user.username, user.password); + + expect(response.statusCode).to.eql(400); + expect(response.body.message).to.eql( + "Both 'filter' and 'ids' are supplied. Define either 'ids' or 'filter' properties in method's arguments" + ); + objectRemover.add(space.id, createdRule1.id, 'rule', 'alerting'); + await getScheduledTask(createdRule1.scheduled_task_id); + }); + + it('should return an error if we pass more than 1000 ids', async () => { + const ids = [...Array(1001)].map((_, i) => `rule${i}`); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids }) + .auth(user.username, user.password); + + expect(response.body).to.eql({ + error: 'Bad Request', + message: '[request body.ids]: array size is [1001], but cannot be greater than [1000]', + statusCode: 400, + }); + }); + + it('should return an error if we do not pass any arguments', async () => { + await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData()) + .expect(200); + + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({}) + .auth(user.username, user.password); + + expect(response.body).to.eql({ + error: 'Bad Request', + message: "Either 'ids' or 'filter' property in method's arguments should be provided", + statusCode: 400, + }); + }); + + it('should return an error if we pass empty ids array', async () => { + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [] }) + .auth(user.username, user.password); + + expect(response.body).to.eql({ + error: 'Bad Request', + message: '[request body.ids]: array size is [0], but cannot be smaller than [1]', + statusCode: 400, + }); + }); + + it('should return an error if we pass empty string instead of fiter', async () => { + const response = await supertestWithoutAuth + .patch(`${getUrlPrefix(space.id)}/internal/alerting/rules/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ filter: '' }) + .auth(user.username, user.password); + + expect(response.body).to.eql({ + error: 'Bad Request', + message: "Either 'ids' or 'filter' property in method's arguments should be provided", + statusCode: 400, + }); + }); + }); + }); +}; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts index 53b7e8e3fb2c0..6265cb7d34ff9 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts @@ -31,6 +31,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./get_alert_summary')); loadTestFile(require.resolve('./rule_types')); loadTestFile(require.resolve('./bulk_edit')); + loadTestFile(require.resolve('./bulk_delete')); loadTestFile(require.resolve('./retain_api_key')); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases/cases_webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases/cases_webhook.ts index d56da37907974..481f82546f7be 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases/cases_webhook.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases/cases_webhook.ts @@ -456,7 +456,7 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) { expect(resp.body).to.eql({ connector_id: simulatedActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: '[Action][Webhook - Case Management]: Unable to create case. Error: JSON Error: Create case JSON body must be valid JSON. ', @@ -486,7 +486,7 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) { expect(resp.body).to.eql({ connector_id: simulatedActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: '[Action][Webhook - Case Management]: Unable to update case with id 12345. Error: JSON Error: Update case JSON body must be valid JSON. ', @@ -553,7 +553,7 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) { expect(resp.body).to.eql({ connector_id: simulatedActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: '[Action][Webhook - Case Management]: Unable to create comment at case with id 123. Error: JSON Error: Create comment JSON body must be valid JSON. ', @@ -620,7 +620,7 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) { expect(resp.body).to.eql({ connector_id: simulatedActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: '[Action][Webhook - Case Management]: Unable to create case. Error: Invalid Create case URL: Error: Invalid protocol. ', @@ -650,7 +650,7 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) { expect(resp.body).to.eql({ connector_id: simulatedActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: '[Action][Webhook - Case Management]: Unable to update case with id 12345. Error: Invalid Update case URL: Error: Invalid URL. ', @@ -717,7 +717,7 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) { expect(resp.body).to.eql({ connector_id: simulatedActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: '[Action][Webhook - Case Management]: Unable to create comment at case with id 123. Error: Invalid Create comment URL: Error: Invalid URL. ', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/stack/opsgenie.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/stack/opsgenie.ts index e1011059b8532..adce611ec3d1d 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/stack/opsgenie.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/stack/opsgenie.ts @@ -181,7 +181,7 @@ export default function opsgenieTest({ getService }: FtrProviderContext) { expect(body).to.eql({ connector_id: opsgenieActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: `Sub action "invalidAction" is not registered. Connector id: ${opsgenieActionId}. Connector name: Opsgenie. Connector type: .opsgenie`, }); @@ -199,7 +199,7 @@ export default function opsgenieTest({ getService }: FtrProviderContext) { expect(body).to.eql({ connector_id: opsgenieActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: 'Request validation failed (Error: [message]: expected value of type [string] but got [undefined])', @@ -218,7 +218,7 @@ export default function opsgenieTest({ getService }: FtrProviderContext) { expect(body).to.eql({ connector_id: opsgenieActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: 'Request validation failed (Error: [alias]: expected value of type [string] but got [undefined])', @@ -250,7 +250,7 @@ export default function opsgenieTest({ getService }: FtrProviderContext) { expect(body).to.eql({ connector_id: opsgenieActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: 'Request validation failed (Error: [responders.0]: types that failed validation:\n- [responders.0.0.type]: types that failed validation:\n - [responders.0.type.0]: expected value to equal [team]\n - [responders.0.type.1]: expected value to equal [user]\n - [responders.0.type.2]: expected value to equal [escalation]\n - [responders.0.type.3]: expected value to equal [schedule]\n- [responders.0.1.id]: expected value of type [string] but got [undefined])', @@ -279,7 +279,7 @@ export default function opsgenieTest({ getService }: FtrProviderContext) { expect(body).to.eql({ connector_id: opsgenieActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: 'Request validation failed (Error: [responders.0]: types that failed validation:\n- [responders.0.0.name]: expected value of type [string] but got [undefined]\n- [responders.0.1.id]: expected value of type [string] but got [undefined])', @@ -381,7 +381,7 @@ export default function opsgenieTest({ getService }: FtrProviderContext) { expect(body).to.eql({ connector_id: opsgenieActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: 'Request validation failed (Error: [visibleTo.0]: types that failed validation:\n- [visibleTo.0.0.type]: expected value to equal [team]\n- [visibleTo.0.1.id]: expected value of type [string] but got [undefined]\n- [visibleTo.0.2.id]: expected value of type [string] but got [undefined]\n- [visibleTo.0.3.username]: expected value of type [string] but got [undefined])', @@ -445,7 +445,7 @@ export default function opsgenieTest({ getService }: FtrProviderContext) { expect(body).to.eql({ connector_id: opsgenieActionId, status: 'error', - retry: false, + retry: true, message: 'an error occurred while running the action', service_message: 'Request validation failed (Error: [details.bananas]: expected value of type [string] but got [number])', @@ -680,7 +680,7 @@ export default function opsgenieTest({ getService }: FtrProviderContext) { expect(body).to.eql({ status: 'error', message: 'an error occurred while running the action', - retry: false, + retry: true, connector_id: opsgenieActionId, service_message: 'Status code: undefined. Message: Message: failed', }); @@ -702,7 +702,7 @@ export default function opsgenieTest({ getService }: FtrProviderContext) { expect(body).to.eql({ status: 'error', message: 'an error occurred while running the action', - retry: false, + retry: true, connector_id: opsgenieActionId, service_message: 'Status code: undefined. Message: Message: failed', }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/get_all.ts index 69f618c804eb1..d4bfe3cbdd704 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/get_all.ts @@ -127,6 +127,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, }, + { + id: 'my-test-email', + is_preconfigured: true, + is_deprecated: false, + connector_type_id: '.email', + name: 'TestEmail#xyz', + referenced_by_count: 0, + }, ]); break; default: @@ -262,6 +270,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, }, + { + id: 'my-test-email', + is_preconfigured: true, + is_deprecated: false, + connector_type_id: '.email', + name: 'TestEmail#xyz', + referenced_by_count: 0, + }, ]); break; default: @@ -361,6 +377,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, }, + { + id: 'my-test-email', + is_preconfigured: true, + is_deprecated: false, + connector_type_id: '.email', + name: 'TestEmail#xyz', + referenced_by_count: 0, + }, ]); break; default: diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/sub_action_framework/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/sub_action_framework/index.ts index 350361d58a395..bbf97b016f2ba 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/sub_action_framework/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/sub_action_framework/index.ts @@ -166,7 +166,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { expect(execRes.body).to.eql({ status: 'error', message: 'an error occurred while running the action', - retry: false, + retry: true, connector_id: res.body.id, service_message: 'Request validation failed (Error: [id]: expected value of type [string] but got [undefined])', @@ -245,7 +245,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { expect(execRes.body).to.eql({ status: 'error', message: 'an error occurred while running the action', - retry: false, + retry: true, connector_id: res.body.id, service_message: `Sub action \"notRegistered\" is not registered. Connector id: ${res.body.id}. Connector name: Test: Sub action connector. Connector type: .test-sub-action-connector`, }); @@ -265,7 +265,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { expect(execRes.body).to.eql({ status: 'error', message: 'an error occurred while running the action', - retry: false, + retry: true, connector_id: res.body.id, service_message: `Method \"notAFunction\" does not exists in service. Sub action: \"notAFunction\". Connector id: ${res.body.id}. Connector name: Test: Sub action connector. Connector type: .test-sub-action-connector`, }); @@ -285,7 +285,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { expect(execRes.body).to.eql({ status: 'error', message: 'an error occurred while running the action', - retry: false, + retry: true, connector_id: res.body.id, service_message: `Method \"notExist\" does not exists in service. Sub action: \"notExist\". Connector id: ${res.body.id}. Connector name: Test: Sub action connector. Connector type: .test-sub-action-connector`, }); @@ -308,7 +308,7 @@ export default function createActionTests({ getService }: FtrProviderContext) { expect(execRes.body).to.eql({ status: 'error', message: 'an error occurred while running the action', - retry: false, + retry: true, connector_id: res.body.id, service_message: 'You should register at least one subAction for your connector type', }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts index b4cb36ab59d85..707f15534e663 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts @@ -571,7 +571,7 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F expect(taskState).not.to.be(undefined); actionsTelemetry = JSON.parse(taskState!); expect(actionsTelemetry.runs).to.equal(2); - expect(actionsTelemetry.count_total).to.equal(19); + expect(actionsTelemetry.count_total).to.equal(20); }); // request alerting telemetry task to run diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts b/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts index ae8d52ecd5313..91d809bff04ca 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts @@ -196,7 +196,7 @@ const NoKibanaPrivilegesAtSpace1: NoKibanaPrivilegesAtSpace1 = { interface SuperuserAtSpace1 extends Scenario { id: 'superuser at space1'; } -const SuperuserAtSpace1: SuperuserAtSpace1 = { +export const SuperuserAtSpace1: SuperuserAtSpace1 = { id: 'superuser at space1', user: Superuser, space: Space1, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/enqueue.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/enqueue.ts index 084c105aa723a..9f0eea9d91929 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/enqueue.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/enqueue.ts @@ -70,7 +70,7 @@ export default function ({ getService }: FtrProviderContext) { await esTestIndexTool.waitForDocs('action:test.index-record', reference, 1); }); - it('should cleanup task after a failure', async () => { + it('should retry task after a failure', async () => { const testStart = new Date().toISOString(); const { body: createdAction } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) @@ -85,6 +85,7 @@ export default function ({ getService }: FtrProviderContext) { objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions'); const reference = `actions-enqueue-2:${Spaces.space1.id}:${createdAction.id}`; + let runAt: number; await supertest .post( `${getUrlPrefix(Spaces.space1.id)}/api/alerts_fixture/${createdAction.id}/enqueue_action` @@ -96,7 +97,10 @@ export default function ({ getService }: FtrProviderContext) { index: ES_TEST_INDEX_NAME, }, }) - .expect(204); + .expect(204) + .then(() => { + runAt = Date.now(); + }); await esTestIndexTool.waitForDocs('action:test.failing', reference, 1); await retry.try(async () => { @@ -123,7 +127,9 @@ export default function ({ getService }: FtrProviderContext) { }, }, }); - expect((searchResult.hits.total as estypes.SearchTotalHits).value).to.eql(0); + const hit = searchResult.hits.hits as Array<estypes.SearchHit<any>>; + expect(Date.parse(hit[0]._source.task.runAt)).to.greaterThan(runAt); + expect(Date.parse(hit[0]._source.task.attempts)).to.greaterThan(1); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts index c6330e660aa24..40b574063fbe5 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts @@ -133,7 +133,7 @@ export default function ({ getService }: FtrProviderContext) { status: 'error', message: 'an error occurred while running the action', service_message: `expected failure for ${ES_TEST_INDEX_NAME} ${reference}`, - retry: false, + retry: true, }); await validateEventLog({ @@ -142,7 +142,7 @@ export default function ({ getService }: FtrProviderContext) { actionTypeId: 'test.failing', outcome: 'failure', message: `action execution failure: test.failing:${createdAction.id}: failing action`, - errorMessage: `an error occurred while running the action: expected failure for .kibana-alerting-test-data actions-failure-1:space1`, + errorMessage: `an error occurred while running the action: expected failure for .kibana-alerting-test-data actions-failure-1:space1; retry: true`, }); }); @@ -327,7 +327,7 @@ export default function ({ getService }: FtrProviderContext) { status: 'error', message: 'an error occurred while running the action', serviceMessage: `expected failure for ${ES_TEST_INDEX_NAME} ${reference}`, - retry: false, + retry: true, }); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts index 0632f48ed6e8d..7846c9512867e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/get_all.ts @@ -115,6 +115,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, }, + { + id: 'my-test-email', + is_preconfigured: true, + is_deprecated: false, + connector_type_id: '.email', + name: 'TestEmail#xyz', + referenced_by_count: 0, + }, ]); }); @@ -202,6 +210,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referenced_by_count: 0, }, + { + id: 'my-test-email', + is_preconfigured: true, + is_deprecated: false, + connector_type_id: '.email', + name: 'TestEmail#xyz', + referenced_by_count: 0, + }, ]); }); @@ -302,6 +318,14 @@ export default function getAllActionTests({ getService }: FtrProviderContext) { name: 'Test:_Preconfigured_Index_Record', referencedByCount: 0, }, + { + id: 'my-test-email', + isPreconfigured: true, + isDeprecated: false, + actionTypeId: '.email', + name: 'TestEmail#xyz', + referencedByCount: 0, + }, ]); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts index 866f13ed5294c..b4dbb42e8f993 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts @@ -28,6 +28,7 @@ export default function actionsTests({ loadTestFile, getService }: FtrProviderCo loadTestFile(require.resolve('./connector_types/stack/webhook')); loadTestFile(require.resolve('./connector_types/stack/preconfigured_alert_history_connector')); loadTestFile(require.resolve('./type_not_enabled')); + loadTestFile(require.resolve('./schedule_unsecured_action')); // note that this test will destroy existing spaces loadTestFile(require.resolve('./migrations')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/schedule_unsecured_action.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/schedule_unsecured_action.ts new file mode 100644 index 0000000000000..9a5719b7fa700 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/schedule_unsecured_action.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 expect from '@kbn/expect'; +import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { Spaces } from '../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover } from '../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function createUnsecuredActionTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); + const es = getService('es'); + const retry = getService('retry'); + + describe('schedule unsecured action', () => { + const objectRemover = new ObjectRemover(supertest); + + // need to wait for kibanaServer to settle ... + before(() => { + kibanaServer.resolveUrl(`/api/sample_unsecured_action`); + }); + + after(() => objectRemover.removeAll()); + + it('should successfully schedule email action', async () => { + const testStart = new Date().toISOString(); + const { body: result } = await supertest + .post(`/api/sample_unsecured_action`) + .set('kbn-xsrf', 'xxx') + .send({ + requesterId: 'functional_tester', + id: 'my-test-email', + params: { + to: ['you@test.com'], + subject: 'hello from Kibana!', + message: 'does this work??', + }, + }) + .expect(200); + expect(result.status).to.eql('success'); + + await retry.try(async () => { + const searchResult = await es.search({ + index: '.kibana-event-log*', + body: { + query: { + bool: { + filter: [ + { + term: { + 'event.provider': { + value: 'actions', + }, + }, + }, + { + term: { + 'event.action': 'execute', + }, + }, + { + range: { + '@timestamp': { + gte: testStart, + }, + }, + }, + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + filter: [ + { + term: { + 'kibana.saved_objects.id': { + value: 'my-test-email', + }, + }, + }, + { + term: { + 'kibana.saved_objects.type': 'action', + }, + }, + ], + }, + }, + }, + }, + ], + }, + }, + }, + }); + expect((searchResult.hits.total as SearchTotalHits).value).to.eql(1); + + const hit = searchResult.hits.hits[0]; + // @ts-expect-error _source: unknown + expect(hit?._source?.event?.outcome).to.eql('success'); + // @ts-expect-error _source: unknown + expect(hit?._source?.message).to.eql( + `action executed: .email:my-test-email: TestEmail#xyz` + ); + }); + }); + + it('should not allow scheduling email action from unallowed requester', async () => { + const { body: result } = await supertest + .post(`/api/sample_unsecured_action`) + .set('kbn-xsrf', 'xxx') + .send({ + requesterId: 'not_allowed', + id: 'my-test-email', + params: { + to: ['you@test.com'], + subject: 'hello from Kibana!', + message: 'does this work??', + }, + }) + .expect(200); + expect(result.status).to.eql('error'); + expect(result.error).to.eql( + `Error: "not_allowed" feature is not allow-listed for UnsecuredActionsClient access.` + ); + }); + + it('should not allow scheduling action from unallowed connector types', async () => { + const { body: result } = await supertest + .post(`/api/sample_unsecured_action`) + .set('kbn-xsrf', 'xxx') + .send({ + requesterId: 'functional_tester', + id: 'my-slack1', + params: { + message: 'does this work??', + }, + }) + .expect(200); + expect(result.status).to.eql('error'); + expect(result.error).to.eql( + `Error: .slack actions cannot be scheduled for unsecured actions execution` + ); + }); + + it('should not allow scheduling action from non preconfigured connectors', async () => { + const response = await supertest + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My email action', + connector_type_id: '.email', + config: { + from: 'me@test.com', + service: '__json', + }, + secrets: { + user: 'user', + password: 'password', + }, + }); + expect(response.status).to.eql(200); + + const connectorId = response.body.id; + objectRemover.add(Spaces.space1.id, connectorId, 'action', 'actions'); + const { body: result } = await supertest + .post(`/api/sample_unsecured_action`) + .set('kbn-xsrf', 'xxx') + .send({ + requesterId: 'functional_tester', + id: connectorId, + params: { + to: ['you@test.com'], + subject: 'hello from Kibana!', + message: 'does this work??', + }, + }) + .expect(200); + expect(result.status).to.eql('error'); + expect(result.error).to.eql( + `Error: ${connectorId} are not preconfigured connectors and can't be scheduled for unsecured actions execution` + ); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_action_error_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_action_error_log.ts index 19c2270d07880..78d6a09ac41e3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_action_error_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_action_error_log.ts @@ -115,7 +115,7 @@ export default function createGetActionErrorLogTests({ getService }: FtrProvider for (const errors of response.body.errors) { expect(errors.type).to.equal('actions'); expect(errors.message).to.equal( - `action execution failure: test.throw:${createdConnector.id}: connector that throws - an error occurred while running the action: this action is intended to fail` + `action execution failure: test.throw:${createdConnector.id}: connector that throws - an error occurred while running the action: this action is intended to fail; retry: true` ); } }); diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index 78fb376fa89b8..8b98fc9422c06 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -107,9 +107,9 @@ export function createTestConfig(config: ApmFtrConfig) { kibanaServer, username: ApmUsername.apmManageOwnAndCreateAgentKeys, }), - monitorIndicesUser: await getApmApiClient({ + monitorClusterAndIndicesUser: await getApmApiClient({ kibanaServer, - username: ApmUsername.apmMonitorIndices, + username: ApmUsername.apmMonitorClusterAndIndices, }), }; }, diff --git a/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/metrics_charts.spec.ts similarity index 100% rename from x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.spec.ts rename to x-pack/test/apm_api_integration/tests/metrics/metrics_charts.spec.ts diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts new file mode 100644 index 0000000000000..07648d4e548a2 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/generate_data.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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'; +import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; + +export const config = { + memoryTotal: 536870912, // 0.5gb + memoryFree: 94371840, // ~0.08 gb + billedDurationMs: 4000, + faasTimeoutMs: 10000, + coldStartDurationPython: 4000, + faasDuration: 4000, + transactionDuration: 1000, + pythonServerlessFunctionNames: ['fn-lambda-python', 'fn-lambda-python-2'], + pythonInstanceName: 'instance_A', + serverlessId: 'arn:aws:lambda:us-west-2:001:function:', +}; + +export const expectedValues = { + expectedMemoryUsedRate: (config.memoryTotal - config.memoryFree) / config.memoryTotal, + expectedMemoryUsed: config.memoryTotal - config.memoryFree, +}; + +export async function generateData({ + synthtraceEsClient, + start, + end, +}: { + synthtraceEsClient: ApmSynthtraceEsClient; + start: number; + end: number; +}) { + const { + memoryTotal, + memoryFree, + billedDurationMs, + faasTimeoutMs, + coldStartDurationPython, + faasDuration, + transactionDuration, + pythonServerlessFunctionNames, + pythonInstanceName, + } = config; + + const cloudFields = { + 'cloud.provider': 'aws', + 'cloud.service.name': 'lambda', + 'cloud.region': 'us-west-2', + }; + + const [instanceLambdaPython, instanceLambdaPython2] = pythonServerlessFunctionNames.map( + (functionName) => { + return apm + .serverlessFunction({ + serviceName: 'lambda-python', + environment: 'test', + agentName: 'python', + functionName, + }) + .instance({ instanceName: pythonInstanceName, ...cloudFields }); + } + ); + + const instanceLambdaNode = apm + .serverlessFunction({ + serviceName: 'lambda-node', + environment: 'test', + agentName: 'nodejs', + functionName: 'fn-lambda-node', + }) + .instance({ instanceName: 'instance_B', ...cloudFields }); + + const systemMemory = { + free: memoryFree, + total: memoryTotal, + }; + + const transactionsEvents = timerange(start, end) + .ratePerMinute(1) + .generator((timestamp) => [ + instanceLambdaPython + .invocation() + .billedDuration(billedDurationMs) + .coldStart(true) + .coldStartDuration(coldStartDurationPython) + .faasDuration(faasDuration) + .faasTimeout(faasTimeoutMs) + .memory(systemMemory) + .timestamp(timestamp) + .duration(transactionDuration) + .success(), + instanceLambdaPython2 + .invocation() + .billedDuration(billedDurationMs) + .coldStart(true) + .coldStartDuration(coldStartDurationPython) + .faasDuration(faasDuration) + .faasTimeout(faasTimeoutMs) + .memory(systemMemory) + .timestamp(timestamp) + .duration(transactionDuration) + .success(), + instanceLambdaNode + .invocation() + .billedDuration(billedDurationMs) + .coldStart(false) + .faasDuration(faasDuration) + .faasTimeout(faasTimeoutMs) + .memory(systemMemory) + .timestamp(timestamp) + .duration(transactionDuration) + .success(), + ]); + + await synthtraceEsClient.index(transactionsEvents); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.ts new file mode 100644 index 0000000000000..4861313c377f1 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_active_instances.spec.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 { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import expect from '@kbn/expect'; +import { sumBy } from 'lodash'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { config, expectedValues, generateData } from './generate_data'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + const numberOfTransactionsCreated = 15; + + async function callApi(serviceName: string, serverlessId?: string) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances`, + params: { + path: { serviceName }, + query: { + environment: 'test', + kuery: '', + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + ...(serverlessId ? { serverlessId } : {}), + }, + }, + }); + } + + registry.when('Serverless active instances', { config: 'basic', archives: [] }, () => { + const { + memoryTotal, + billedDurationMs, + pythonServerlessFunctionNames, + faasDuration, + serverlessId, + } = config; + + const { expectedMemoryUsed } = expectedValues; + + before(async () => { + await generateData({ start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('Python service', () => { + let activeInstances: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances'>; + before(async () => { + const response = await callApi('lambda-python'); + activeInstances = response.body; + }); + + it('returns correct values for all serverless functions', () => { + pythonServerlessFunctionNames.forEach((name) => { + const activeInstanceOverview = activeInstances.activeInstances.find( + (item) => item.serverlessFunctionName === name + ); + + expect(activeInstanceOverview?.serverlessId).to.eql(`${serverlessId}${name}`); + expect(activeInstanceOverview?.serverlessDurationAvg).to.eql(faasDuration); + expect(activeInstanceOverview?.billedDurationAvg).to.eql(billedDurationMs); + expect(activeInstanceOverview?.avgMemoryUsed).to.eql(expectedMemoryUsed); + expect(activeInstanceOverview?.memorySize).to.eql(memoryTotal); + }); + }); + describe('timeseries', () => { + it('returns correct sum value', () => { + const sumValue = sumBy( + activeInstances?.timeseries?.filter((item) => item.y !== 0), + 'y' + ); + expect(sumValue).to.equal(numberOfTransactionsCreated); + }); + }); + }); + + describe('detailed metrics', () => { + let activeInstances: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/active_instances'>; + before(async () => { + const response = await callApi( + 'lambda-python', + `${serverlessId}${pythonServerlessFunctionNames[0]}` + ); + activeInstances = response.body; + }); + + it('returns correct values for all serverless functions', () => { + const activeInstanceOverview = activeInstances.activeInstances.find( + (item) => item.serverlessFunctionName === pythonServerlessFunctionNames[0] + ); + + expect(activeInstanceOverview?.serverlessId).to.eql( + `${serverlessId}${pythonServerlessFunctionNames[0]}` + ); + expect(activeInstanceOverview?.serverlessDurationAvg).to.eql(faasDuration); + expect(activeInstanceOverview?.billedDurationAvg).to.eql(billedDurationMs); + expect(activeInstanceOverview?.avgMemoryUsed).to.eql(expectedMemoryUsed); + expect(activeInstanceOverview?.memorySize).to.eql(memoryTotal); + }); + describe('timeseries', () => { + it('returns correct sum value', () => { + const sumValue = sumBy( + activeInstances?.timeseries?.filter((item) => item.y !== 0), + 'y' + ); + expect(sumValue).to.equal(numberOfTransactionsCreated); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.ts new file mode 100644 index 0000000000000..e58df3291f492 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_functions_overview.spec.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 { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { config, expectedValues, generateData } from './generate_data'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + const numberOfTransactionsCreated = 15; + + async function callApi(serviceName: string) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview`, + params: { + path: { serviceName }, + query: { + environment: 'test', + kuery: '', + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + }, + }, + }); + } + + registry.when('Serverless functions overview', { config: 'basic', archives: [] }, () => { + const { + memoryTotal, + billedDurationMs, + pythonServerlessFunctionNames, + faasDuration, + serverlessId, + } = config; + const { expectedMemoryUsed } = expectedValues; + + before(async () => { + await generateData({ start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('Python service', () => { + let functionsOverview: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/functions_overview'>; + before(async () => { + const response = await callApi('lambda-python'); + functionsOverview = response.body; + }); + it('returns correct number of serverless functions', () => { + expect( + functionsOverview.serverlessFunctionsOverview.map((item) => { + return item.serverlessFunctionName; + }) + ).to.eql(pythonServerlessFunctionNames); + }); + it('returns correct values for all serverless functions', () => { + pythonServerlessFunctionNames.forEach((name) => { + const functionOverview = functionsOverview.serverlessFunctionsOverview.find( + (item) => item.serverlessFunctionName === name + ); + + expect(functionOverview?.serverlessId).to.eql(`${serverlessId}${name}`); + expect(functionOverview?.serverlessDurationAvg).to.eql(faasDuration); + expect(functionOverview?.billedDurationAvg).to.eql(billedDurationMs); + expect(functionOverview?.coldStartCount).to.eql(numberOfTransactionsCreated); + expect(functionOverview?.avgMemoryUsed).to.eql(expectedMemoryUsed); + expect(functionOverview?.memorySize).to.eql(memoryTotal); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts new file mode 100644 index 0000000000000..f660e218eba1b --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_metrics_charts.spec.ts @@ -0,0 +1,322 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { meanBy, sumBy } from 'lodash'; +import { Coordinate } from '@kbn/apm-plugin/typings/timeseries'; +import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { generateData, config } from './generate_data'; + +function isNotNullOrZeroCoordinate(coordinate: Coordinate) { + return coordinate.y !== null && coordinate.y !== 0; +} + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + const numberOfTransactionsCreated = 15; + + async function callApi(serviceName: string, serverlessId?: string) { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/metrics/serverless/charts', + params: { + path: { serviceName }, + query: { + environment: 'test', + kuery: '', + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + ...(serverlessId ? { serverlessId } : {}), + }, + }, + }); + } + + registry.when( + 'Serverless metrics charts when data is not loaded', + { config: 'basic', archives: [] }, + () => { + let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessMetrics = response.body; + }); + + it('returns empty', () => { + serverlessMetrics.charts.forEach((chart) => { + expect(chart.series).to.be.empty(); + }); + }); + } + ); + + registry.when('Serverless metrics charts', { config: 'basic', archives: [] }, () => { + const { + memoryTotal, + memoryFree, + billedDurationMs, + coldStartDurationPython, + transactionDuration, + pythonServerlessFunctionNames, + serverlessId, + } = config; + + before(async () => { + await generateData({ start, end, synthtraceEsClient }); + }); + + after(() => synthtraceEsClient.clean()); + + describe('Python service', () => { + let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessMetrics = response.body; + }); + + it('returns all metrics chart', () => { + expect(serverlessMetrics.charts.length).to.be.greaterThan(0); + expect(serverlessMetrics.charts.map(({ title }) => title).sort()).to.eql([ + 'Cold start duration', + 'Cold starts', + 'Compute usage', + 'Lambda Duration', + 'System memory usage', + ]); + }); + + describe('Avg. Duration', () => { + const transactionDurationInMicroSeconds = transactionDuration * 1000; + [ + { title: 'Billed Duration', expectedValue: billedDurationMs * 1000 }, + { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const avgDurationMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'avg_duration'; + }); + const series = avgDurationMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + let metricsChart: typeof serverlessMetrics.charts[0] | undefined; + + describe('Cold start duration', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_duration'; + }); + }); + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal(coldStartDurationPython * 1000); + }); + + it('returns correct mean value', () => { + const meanValue = meanBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(meanValue).to.equal(coldStartDurationPython * 1000); + }); + }); + + describe('Cold start count', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_count'; + }); + }); + + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal( + numberOfTransactionsCreated * pythonServerlessFunctionNames.length + ); + }); + + it('returns correct sum value', () => { + const sumValue = sumBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(sumValue).to.equal( + numberOfTransactionsCreated * pythonServerlessFunctionNames.length + ); + }); + }); + + describe('memory usage', () => { + const expectedFreeMemory = 1 - memoryFree / memoryTotal; + [ + { title: 'Max', expectedValue: expectedFreeMemory }, + { title: 'Average', expectedValue: expectedFreeMemory }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const memoryUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'memory_usage_chart'; + }); + const series = memoryUsageMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + describe('Compute usage', () => { + const GBSeconds = 1024 * 1024 * 1024 * 1000; + const expectedValue = (memoryTotal * billedDurationMs) / GBSeconds; + let computeUsageMetric: typeof serverlessMetrics.charts[0] | undefined; + before(() => { + computeUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'compute_usage'; + }); + }); + it('returns correct overall value', () => { + expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); + }); + + it('returns correct mean value', () => { + const meanValue = meanBy( + computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), + 'y' + ); + expect(meanValue).to.equal(expectedValue); + }); + }); + }); + + describe('detailed metrics', () => { + let serverlessMetrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/charts'>; + before(async () => { + const response = await callApi( + 'lambda-python', + `${serverlessId}${pythonServerlessFunctionNames[0]}` + ); + serverlessMetrics = response.body; + }); + + it('returns all metrics chart', () => { + expect(serverlessMetrics.charts.length).to.be.greaterThan(0); + expect(serverlessMetrics.charts.map(({ title }) => title).sort()).to.eql([ + 'Cold start duration', + 'Cold starts', + 'Compute usage', + 'Lambda Duration', + 'System memory usage', + ]); + }); + + describe('Avg. Duration', () => { + const transactionDurationInMicroSeconds = transactionDuration * 1000; + [ + { title: 'Billed Duration', expectedValue: billedDurationMs * 1000 }, + { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const avgDurationMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'avg_duration'; + }); + const series = avgDurationMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + let metricsChart: typeof serverlessMetrics.charts[0] | undefined; + + describe('Cold start duration', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_duration'; + }); + }); + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal(coldStartDurationPython * 1000); + }); + + it('returns correct mean value', () => { + const meanValue = meanBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(meanValue).to.equal(coldStartDurationPython * 1000); + }); + }); + + describe('Cold start count', () => { + before(() => { + metricsChart = serverlessMetrics.charts.find((chart) => { + return chart.key === 'cold_start_count'; + }); + }); + + it('returns correct overall value', () => { + expect(metricsChart?.series[0].overallValue).to.equal(numberOfTransactionsCreated); + }); + + it('returns correct sum value', () => { + const sumValue = sumBy( + metricsChart?.series[0]?.data.filter(isNotNullOrZeroCoordinate), + 'y' + ); + expect(sumValue).to.equal(numberOfTransactionsCreated); + }); + }); + + describe('memory usage', () => { + const expectedFreeMemory = 1 - memoryFree / memoryTotal; + [ + { title: 'Max', expectedValue: expectedFreeMemory }, + { title: 'Average', expectedValue: expectedFreeMemory }, + ].map(({ title, expectedValue }) => + it(`returns correct ${title} value`, () => { + const memoryUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'memory_usage_chart'; + }); + const series = memoryUsageMetric?.series.find((item) => item.title === title); + expect(series?.overallValue).to.eql(expectedValue); + const meanValue = meanBy(series?.data.filter(isNotNullOrZeroCoordinate), 'y'); + expect(meanValue).to.eql(expectedValue); + }) + ); + }); + + describe('Compute usage', () => { + const GBSeconds = 1024 * 1024 * 1024 * 1000; + const expectedValue = (memoryTotal * billedDurationMs) / GBSeconds; + let computeUsageMetric: typeof serverlessMetrics.charts[0] | undefined; + before(() => { + computeUsageMetric = serverlessMetrics.charts.find((chart) => { + return chart.key === 'compute_usage'; + }); + }); + it('returns correct overall value', () => { + expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); + }); + + it('returns correct mean value', () => { + const meanValue = meanBy( + computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), + 'y' + ); + expect(meanValue).to.equal(expectedValue); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts new file mode 100644 index 0000000000000..eff5e11278018 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics/serverless/serverless_summary.spec.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { config, expectedValues, generateData } from './generate_data'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const apmApiClient = getService('apmApiClient'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + async function callApi(serviceName: string, serverlessId?: string) { + return await apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/metrics/serverless/summary`, + params: { + path: { serviceName }, + query: { + environment: 'test', + kuery: '', + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + ...(serverlessId ? { serverlessId } : {}), + }, + }, + }); + } + + registry.when( + 'Serverless overview when data is not loaded', + { config: 'basic', archives: [] }, + () => { + let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessSummary = response.body; + }); + + it('returns empty', () => { + expect(serverlessSummary).to.be.empty(); + }); + } + ); + + registry.when('Serverless overview', { config: 'basic', archives: [] }, () => { + const { billedDurationMs, pythonServerlessFunctionNames, faasDuration, serverlessId } = config; + const { expectedMemoryUsedRate } = expectedValues; + + before(async () => { + await generateData({ start, end, synthtraceEsClient }); + }); + + // after(() => synthtraceEsClient.clean()); + + describe('Python service', () => { + let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; + before(async () => { + const response = await callApi('lambda-python'); + serverlessSummary = response.body; + }); + + it('returns correct memory avg', () => { + expect(serverlessSummary.memoryUsageAvgRate).to.eql(expectedMemoryUsedRate); + }); + it('returns correct serverless function total', () => { + expect(serverlessSummary.serverlessFunctionsTotal).to.eql( + pythonServerlessFunctionNames.length + ); + }); + it('returns correct serverless duration avg', () => { + expect(serverlessSummary.serverlessDurationAvg).to.eql(faasDuration); + }); + it('returns correct billed duration avg', () => { + expect(serverlessSummary.billedDurationAvg).to.eql(billedDurationMs); + }); + }); + + describe('detailed metrics', () => { + let serverlessSummary: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/serverless/summary'>; + before(async () => { + const response = await callApi( + 'lambda-python', + `${serverlessId}${pythonServerlessFunctionNames[0]}` + ); + serverlessSummary = response.body; + }); + + it('returns correct memory avg', () => { + expect(serverlessSummary.memoryUsageAvgRate).to.eql(expectedMemoryUsedRate); + }); + it('returns correct serverless function total', () => { + expect(serverlessSummary.serverlessFunctionsTotal).to.eql(1); + }); + it('returns correct serverless duration avg', () => { + expect(serverlessSummary.serverlessDurationAvg).to.eql(faasDuration); + }); + it('returns correct billed duration avg', () => { + expect(serverlessSummary.billedDurationAvg).to.eql(billedDurationMs); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics_charts/serverless.spec.ts b/x-pack/test/apm_api_integration/tests/metrics_charts/serverless.spec.ts deleted file mode 100644 index be56277b5fad1..0000000000000 --- a/x-pack/test/apm_api_integration/tests/metrics_charts/serverless.spec.ts +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { apm, timerange } from '@kbn/apm-synthtrace'; -import expect from '@kbn/expect'; -import { meanBy, sumBy } from 'lodash'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const synthtraceEsClient = getService('synthtraceEsClient'); - - const start = new Date('2021-01-01T00:00:00.000Z').getTime(); - const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; - - async function callApi(serviceName: string, agentName: string) { - return await apmApiClient.readUser({ - endpoint: `GET /internal/apm/services/{serviceName}/metrics/charts`, - params: { - path: { serviceName }, - query: { - environment: 'test', - agentName, - kuery: '', - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - serviceRuntimeName: 'aws_lambda', - }, - }, - }); - } - - registry.when( - 'Serverless metrics charts when data is loaded', - { config: 'basic', archives: [] }, - () => { - const MEMORY_TOTAL = 536870912; // 0.5gb; - const MEMORY_FREE = 94371840; // ~0.08 gb; - const BILLED_DURATION_MS = 4000; - const FAAS_TIMEOUT_MS = 10000; - const COLD_START_DURATION_PYTHON = 4000; - const COLD_START_DURATION_NODE = 0; - const FAAS_DURATION = 4000; - const TRANSACTION_DURATION = 1000; - - const numberOfTransactionsCreated = 15; - const numberOfPythonInstances = 2; - - before(async () => { - const cloudFields = { - 'cloud.provider': 'aws', - 'cloud.service.name': 'lambda', - 'cloud.region': 'us-west-2', - }; - - const instanceLambdaPython = apm - .serverlessFunction({ - serviceName: 'lambda-python', - environment: 'test', - agentName: 'python', - functionName: 'fn-lambda-python', - }) - .instance({ instanceName: 'instance python', ...cloudFields }); - - const instanceLambdaPython2 = apm - .serverlessFunction({ - serviceName: 'lambda-python', - environment: 'test', - agentName: 'python', - functionName: 'fn-lambda-python-2', - }) - .instance({ instanceName: 'instance python 2', ...cloudFields }); - - const instanceLambdaNode = apm - .serverlessFunction({ - serviceName: 'lambda-node', - environment: 'test', - agentName: 'nodejs', - functionName: 'fn-lambda-node', - }) - .instance({ instanceName: 'instance node', ...cloudFields }); - - const systemMemory = { - free: MEMORY_FREE, - total: MEMORY_TOTAL, - }; - - const transactionsEvents = timerange(start, end) - .interval('1m') - .rate(1) - .generator((timestamp) => [ - instanceLambdaPython - .invocation() - .billedDuration(BILLED_DURATION_MS) - .coldStart(true) - .coldStartDuration(COLD_START_DURATION_PYTHON) - .faasDuration(FAAS_DURATION) - .faasTimeout(FAAS_TIMEOUT_MS) - .memory(systemMemory) - .timestamp(timestamp) - .duration(TRANSACTION_DURATION) - .success(), - instanceLambdaPython2 - .invocation() - .billedDuration(BILLED_DURATION_MS) - .coldStart(true) - .coldStartDuration(COLD_START_DURATION_PYTHON) - .faasDuration(FAAS_DURATION) - .faasTimeout(FAAS_TIMEOUT_MS) - .memory(systemMemory) - .timestamp(timestamp) - .duration(TRANSACTION_DURATION) - .success(), - instanceLambdaNode - .invocation() - .billedDuration(BILLED_DURATION_MS) - .coldStart(false) - .coldStartDuration(COLD_START_DURATION_NODE) - .faasDuration(FAAS_DURATION) - .faasTimeout(FAAS_TIMEOUT_MS) - .memory(systemMemory) - .timestamp(timestamp) - .duration(TRANSACTION_DURATION) - .success(), - ]); - - await synthtraceEsClient.index(transactionsEvents); - }); - - after(() => synthtraceEsClient.clean()); - - describe('python', () => { - let metrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/charts'>; - before(async () => { - const { status, body } = await callApi('lambda-python', 'python'); - - expect(status).to.be(200); - metrics = body; - }); - - it('returns all metrics chart', () => { - expect(metrics.charts.length).to.be.greaterThan(0); - expect(metrics.charts.map(({ title }) => title).sort()).to.eql([ - 'Active instances', - 'Avg. Duration', - 'Cold start', - 'Cold start duration', - 'Compute usage', - 'System memory usage', - ]); - }); - - describe('Avg. Duration', () => { - const transactionDurationInMicroSeconds = TRANSACTION_DURATION * 1000; - [ - { title: 'Billed Duration', expectedValue: BILLED_DURATION_MS * 1000 }, - { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const avgDurationMetric = metrics.charts.find((chart) => { - return chart.key === 'avg_duration'; - }); - const series = avgDurationMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy( - series?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - describe('Cold start duration', () => { - let coldStartDurationMetric: typeof metrics['charts'][0] | undefined; - before(() => { - coldStartDurationMetric = metrics.charts.find((chart) => { - return chart.key === 'cold_start_duration'; - }); - }); - it('returns correct overall value', () => { - expect(coldStartDurationMetric?.series[0].overallValue).to.equal( - COLD_START_DURATION_PYTHON * 1000 - ); - }); - - it('returns correct mean value', () => { - const meanValue = meanBy( - coldStartDurationMetric?.series[0]?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.equal(COLD_START_DURATION_PYTHON * 1000); - }); - }); - - describe('Cold start count', () => { - let coldStartCountMetric: typeof metrics['charts'][0] | undefined; - before(() => { - coldStartCountMetric = metrics.charts.find((chart) => { - return chart.key === 'cold_start_count'; - }); - }); - - it('returns correct overall value', () => { - expect(coldStartCountMetric?.series[0].overallValue).to.equal( - numberOfTransactionsCreated * numberOfPythonInstances - ); - }); - - it('returns correct sum value', () => { - const sumValue = sumBy( - coldStartCountMetric?.series[0]?.data.filter((item) => item.y !== null), - 'y' - ); - expect(sumValue).to.equal(numberOfTransactionsCreated * numberOfPythonInstances); - }); - }); - - describe('memory usage', () => { - const expectedFreeMemory = 1 - MEMORY_FREE / MEMORY_TOTAL; - [ - { title: 'Max', expectedValue: expectedFreeMemory }, - { title: 'Average', expectedValue: expectedFreeMemory }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const memoryUsageMetric = metrics.charts.find((chart) => { - return chart.key === 'memory_usage_chart'; - }); - const series = memoryUsageMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy( - series?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - describe('Compute usage', () => { - const GBSeconds = 1024 * 1024 * 1024 * 1000; - const expectedValue = (MEMORY_TOTAL * BILLED_DURATION_MS) / GBSeconds; - let computeUsageMetric: typeof metrics['charts'][0] | undefined; - before(() => { - computeUsageMetric = metrics.charts.find((chart) => { - return chart.key === 'compute_usage'; - }); - }); - it('returns correct overall value', () => { - expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); - }); - - it('returns correct mean value', () => { - const meanValue = meanBy( - computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), - 'y' - ); - expect(meanValue).to.equal(expectedValue); - }); - }); - - describe('Active instances', () => { - let activeInstancesMetric: typeof metrics['charts'][0] | undefined; - before(() => { - activeInstancesMetric = metrics.charts.find((chart) => { - return chart.key === 'active_instances'; - }); - }); - it('returns correct overall value', () => { - expect(activeInstancesMetric?.series[0].overallValue).to.equal(numberOfPythonInstances); - }); - - it('returns correct sum value', () => { - const sumValue = sumBy( - activeInstancesMetric?.series[0]?.data.filter((item) => item.y !== 0), - 'y' - ); - expect(sumValue).to.equal(numberOfTransactionsCreated * numberOfPythonInstances); - }); - }); - }); - - describe('nodejs', () => { - let metrics: APIReturnType<'GET /internal/apm/services/{serviceName}/metrics/charts'>; - before(async () => { - const { status, body } = await callApi('lambda-node', 'nodejs'); - expect(status).to.be(200); - metrics = body; - }); - - it('returns all metrics chart', () => { - expect(metrics.charts.length).to.be.greaterThan(0); - expect(metrics.charts.map(({ title }) => title).sort()).to.eql([ - 'Active instances', - 'Avg. Duration', - 'Cold start', - 'Cold start duration', - 'Compute usage', - 'System memory usage', - ]); - }); - describe('Avg. Duration', () => { - const transactionDurationInMicroSeconds = TRANSACTION_DURATION * 1000; - [ - { title: 'Billed Duration', expectedValue: BILLED_DURATION_MS * 1000 }, - { title: 'Transaction Duration', expectedValue: transactionDurationInMicroSeconds }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const avgDurationMetric = metrics.charts.find((chart) => { - return chart.key === 'avg_duration'; - }); - const series = avgDurationMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy( - series?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - describe('Cold start duration', () => { - let coldStartDurationMetric: typeof metrics['charts'][0] | undefined; - before(() => { - coldStartDurationMetric = metrics.charts.find((chart) => { - return chart.key === 'cold_start_duration'; - }); - }); - - it('returns 0 overall value', () => { - expect(coldStartDurationMetric?.series[0].overallValue).to.equal( - COLD_START_DURATION_NODE * 1000 - ); - }); - - it('returns 0 mean value', () => { - const meanValue = meanBy( - coldStartDurationMetric?.series[0]?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.equal(COLD_START_DURATION_NODE * 1000); - }); - }); - - describe('Cold start count', () => { - let coldStartCountMetric: typeof metrics['charts'][0] | undefined; - before(() => { - coldStartCountMetric = metrics.charts.find((chart) => { - return chart.key === 'cold_start_count'; - }); - }); - - it('does not return cold start count', () => { - expect(coldStartCountMetric?.series).to.be.empty(); - }); - }); - - describe('memory usage', () => { - const expectedFreeMemory = 1 - MEMORY_FREE / MEMORY_TOTAL; - [ - { title: 'Max', expectedValue: expectedFreeMemory }, - { title: 'Average', expectedValue: expectedFreeMemory }, - ].map(({ title, expectedValue }) => - it(`returns correct ${title} value`, () => { - const memoryUsageMetric = metrics.charts.find((chart) => { - return chart.key === 'memory_usage_chart'; - }); - const series = memoryUsageMetric?.series.find((item) => item.title === title); - expect(series?.overallValue).to.eql(expectedValue); - const meanValue = meanBy( - series?.data.filter((item) => item.y !== null), - 'y' - ); - expect(meanValue).to.eql(expectedValue); - }) - ); - }); - - describe('Compute usage', () => { - const GBSeconds = 1024 * 1024 * 1024 * 1000; - const expectedValue = (MEMORY_TOTAL * BILLED_DURATION_MS) / GBSeconds; - let computeUsageMetric: typeof metrics['charts'][0] | undefined; - before(() => { - computeUsageMetric = metrics.charts.find((chart) => { - return chart.key === 'compute_usage'; - }); - }); - it('returns correct overall value', () => { - expect(computeUsageMetric?.series[0].overallValue).to.equal(expectedValue); - }); - - it('returns correct mean value', () => { - const meanValue = meanBy( - computeUsageMetric?.series[0]?.data.filter((item) => item.y !== 0), - 'y' - ); - expect(meanValue).to.equal(expectedValue); - }); - }); - - describe('Active instances', () => { - let activeInstancesMetric: typeof metrics['charts'][0] | undefined; - before(() => { - activeInstancesMetric = metrics.charts.find((chart) => { - return chart.key === 'active_instances'; - }); - }); - it('returns correct overall value', () => { - // there's only one node instance - expect(activeInstancesMetric?.series[0].overallValue).to.equal(1); - }); - - it('returns correct sum value', () => { - const sumValue = sumBy( - activeInstancesMetric?.series[0]?.data.filter((item) => item.y !== 0), - 'y' - ); - expect(sumValue).to.equal(numberOfTransactionsCreated); - }); - }); - }); - } - ); -} diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/get_services.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/get_services.spec.ts new file mode 100644 index 0000000000000..7d036a7018de0 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/get_services.spec.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { apm, timerange } from '@kbn/apm-synthtrace'; +import expect from '@kbn/expect'; +import { IndexLifecyclePhaseSelectOption } from '@kbn/apm-plugin/common/storage_explorer_types'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const registry = getService('registry'); + const synthtraceEsClient = getService('synthtraceEsClient'); + const apmApiClient = getService('apmApiClient'); + + const start = '2021-01-01T12:00:00.000Z'; + const end = '2021-08-01T12:00:00.000Z'; + + // The terms enum API may return terms from deleted documents + // so we add a prefix to make sure we don't get data from other tests + const SERVICE_NAME_PREFIX = 'storage_explorer_services_'; + + async function getServices({ + environment = 'ENVIRONMENT_ALL', + kuery = '', + indexLifecyclePhase = IndexLifecyclePhaseSelectOption.All, + }: { + environment?: string; + kuery?: string; + indexLifecyclePhase?: IndexLifecyclePhaseSelectOption; + } = {}) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/storage_explorer/get_services', + params: { + query: { + environment, + kuery, + indexLifecyclePhase, + }, + }, + }); + + return response.body.services + .filter((service) => service.serviceName.startsWith(SERVICE_NAME_PREFIX)) + .map((service) => ({ + ...service, + serviceName: service.serviceName.replace(SERVICE_NAME_PREFIX, ''), + })); + } + + registry.when('Get services', { config: 'basic', archives: [] }, () => { + before(async () => { + const serviceA = apm + .service({ name: `${SERVICE_NAME_PREFIX}a`, environment: 'production', agentName: 'java' }) + .instance('a'); + + const serviceB = apm + .service({ name: `${SERVICE_NAME_PREFIX}b`, environment: 'development', agentName: 'go' }) + .instance('b'); + + const serviceC = apm + .service({ name: `${SERVICE_NAME_PREFIX}c`, environment: 'development', agentName: 'go' }) + .instance('c'); + + const eventsWithinTimerange = timerange(new Date(start).getTime(), new Date(end).getTime()) + .interval('15m') + .rate(1) + .generator((timestamp) => [ + serviceA.transaction({ transactionName: 'GET /api' }).duration(1000).timestamp(timestamp), + serviceB.transaction({ transactionName: 'GET /api' }).duration(1000).timestamp(timestamp), + ]); + + const eventsOutsideOfTimerange = timerange( + new Date('2021-01-01T00:00:00.000Z').getTime(), + new Date(start).getTime() - 1 + ) + .interval('15m') + .rate(1) + .generator((timestamp) => + serviceC.transaction({ transactionName: 'GET /api' }).duration(1000).timestamp(timestamp) + ); + + await synthtraceEsClient.index(eventsWithinTimerange.merge(eventsOutsideOfTimerange)); + }); + + after(() => synthtraceEsClient.clean()); + + it('with no kuery, environment or index lifecycle phase set it returns services based on the terms enum API', async () => { + const items = await getServices(); + const serviceNames = items.map((item) => item.serviceName); + expect(serviceNames.sort()).to.eql(['a', 'b', 'c']); + }); + + it('with kuery set does it does not return any services', async () => { + const services = await getServices({ + kuery: 'service.name:*', + }); + expect(services).to.be.empty(); + }); + + it('with environment set to production it does not return any services', async () => { + const services = await getServices({ + environment: 'production', + }); + expect(services).to.be.empty(); + }); + + it('with index lifecycle phase set to hot it does not return any services', async () => { + const services = await getServices({ + indexLifecyclePhase: IndexLifecyclePhaseSelectOption.Hot, + }); + expect(services).to.be.empty(); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts index a9ec2199c8b23..260c22eb68775 100644 --- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_details.spec.ts @@ -36,7 +36,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/storage_details'>['params'] > ) { - return await apmApiClient.monitorIndicesUser({ + return await apmApiClient.monitorClusterAndIndicesUser({ endpoint: 'GET /internal/apm/services/{serviceName}/storage_details', params: { path: { @@ -70,8 +70,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { } ); - registry.when('Storage details', { config: 'basic', archives: [] }, () => { - describe('when data is loaded', () => { + // FLAKY: https://github.com/elastic/kibana/issues/144025 + registry.when.skip('Storage details', { config: 'basic', archives: [] }, () => { + describe.skip('when data is loaded', () => { before(async () => { const serviceGo = apm .service({ name: serviceName, environment: 'production', agentName: 'go' }) diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts index dc2429a5b8b3a..f54149665174b 100644 --- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer.spec.ts @@ -27,7 +27,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { APIClientRequestParamsOf<'GET /internal/apm/storage_explorer'>['params'] > ) { - return await apmApiClient.monitorIndicesUser({ + return await apmApiClient.monitorClusterAndIndicesUser({ endpoint: 'GET /internal/apm/storage_explorer', params: { query: { diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_privileges.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_privileges.spec.ts index 4d7cc8655840f..087bacdc898b3 100644 --- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_privileges.spec.ts +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_privileges.spec.ts @@ -20,7 +20,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { registry.when('Storage explorer privileges', { config: 'basic', archives: [] }, () => { it('returns true when the user has the required indices privileges', async () => { - const { status, body } = await callApi(apmApiClient.monitorIndicesUser); + const { status, body } = await callApi(apmApiClient.monitorClusterAndIndicesUser); expect(status).to.be(200); expect(body.hasPrivileges).to.be(true); }); diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts index afde7d243c227..37a747a3c3e76 100644 --- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_summary_stats.spec.ts @@ -27,7 +27,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { APIClientRequestParamsOf<'GET /internal/apm/storage_explorer_summary_stats'>['params'] > ) { - return await apmApiClient.monitorIndicesUser({ + return await apmApiClient.monitorClusterAndIndicesUser({ endpoint: 'GET /internal/apm/storage_explorer_summary_stats', params: { query: { @@ -53,7 +53,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(status).to.be(200); expect(body.tracesPerMinute).to.be(0); expect(body.numberOfServices).to.be(0); - expect(body.estimatedSize).to.be(0); + expect(body.totalSize).to.be(0); + expect(body.estimatedIncrementalSize).to.be(0); + expect(body.diskSpaceUsedPct).to.be(0); expect(body.dailyDataGeneration).to.be(0); }); } @@ -100,7 +102,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(status).to.be(200); expect(body.numberOfServices).to.be(2); expect(roundNumber(body.tracesPerMinute)).to.be(2); - expect(body.estimatedSize).to.be.greaterThan(0); + expect(body.totalSize).to.be.greaterThan(0); + expect(body.estimatedIncrementalSize).to.be.greaterThan(0); + expect(body.diskSpaceUsedPct).to.be.greaterThan(0); expect(body.dailyDataGeneration).to.be.greaterThan(0); }); @@ -114,7 +118,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(status).to.be(200); expect(body.numberOfServices).to.be(1); expect(roundNumber(body.tracesPerMinute)).to.be(1); - expect(body.estimatedSize).to.be.greaterThan(0); + expect(body.totalSize).to.be.greaterThan(0); + expect(body.estimatedIncrementalSize).to.be.greaterThan(0); + expect(body.diskSpaceUsedPct).to.be.greaterThan(0); expect(body.dailyDataGeneration).to.be.greaterThan(0); }); @@ -128,7 +134,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(status).to.be(200); expect(body.tracesPerMinute).to.be(0); expect(body.numberOfServices).to.be(0); - expect(body.estimatedSize).to.be(0); + expect(body.totalSize).to.be.greaterThan(0); + expect(body.estimatedIncrementalSize).to.be(0); + expect(body.diskSpaceUsedPct).to.be.greaterThan(0); expect(body.dailyDataGeneration).to.be(0); }); @@ -142,7 +150,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(status).to.be(200); expect(body.numberOfServices).to.be(1); expect(roundNumber(body.tracesPerMinute)).to.be(1); - expect(body.estimatedSize).to.be.greaterThan(0); + expect(body.totalSize).to.be.greaterThan(0); + expect(body.estimatedIncrementalSize).to.be.greaterThan(0); + expect(body.diskSpaceUsedPct).to.be.greaterThan(0); expect(body.dailyDataGeneration).to.be.greaterThan(0); }); }); diff --git a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts index 632e17c37509a..f668b0ba71a5c 100644 --- a/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts +++ b/x-pack/test/apm_api_integration/tests/storage_explorer/storage_explorer_timeseries_chart.spec.ts @@ -22,7 +22,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; async function callApi() { - return await apmApiClient.monitorIndicesUser({ + return await apmApiClient.monitorClusterAndIndicesUser({ endpoint: 'GET /internal/apm/storage_chart', params: { query: { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts index 80dffef7cd3ee..403d74e2290d0 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts @@ -506,8 +506,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('400s if the title is too long', async () => { - const longTitle = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nulla enim, rutrum sit amet euismod venenatis, blandit et massa. Nulla id consectetur enim.'; + const longTitle = 'a'.repeat(161); const postedCase = await createCase(supertest, postCaseReq); await updateCase({ diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts index 76759902d7a37..47a1e197ac213 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts @@ -252,8 +252,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('400s if the title is too long', async () => { - const longTitle = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nulla enim, rutrum sit amet euismod venenatis, blandit et massa. Nulla id consectetur enim.'; + const longTitle = 'a'.repeat(161); await createCase(supertest, getPostCaseRequest({ title: longTitle }), 400); }); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts index 70ee954bd9166..7f7d023fe2727 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/add_prepackaged_rules.ts @@ -6,9 +6,12 @@ */ import expect from '@kbn/expect'; -import { PrePackagedRulesAndTimelinesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response'; +import { + PREBUILT_RULES_STATUS_URL, + PREBUILT_RULES_URL, + InstallPrebuiltRulesAndTimelinesResponse, +} from '@kbn/security-solution-plugin/common/detection_engine/prebuilt_rules'; -import { DETECTION_ENGINE_PREPACKAGED_URL } from '@kbn/security-solution-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -43,7 +46,7 @@ export default ({ getService }: FtrProviderContext): void => { await waitFor( async () => { const { body, status } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) + .put(PREBUILT_RULES_URL) .set('kbn-xsrf', 'true') .send(); if (status === 200) { @@ -51,11 +54,11 @@ export default ({ getService }: FtrProviderContext): void => { } return status === 200; }, - DETECTION_ENGINE_PREPACKAGED_URL, + PREBUILT_RULES_URL, log ); - const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + const prepackagedRules = responseBody as InstallPrebuiltRulesAndTimelinesResponse; expect(prepackagedRules.rules_installed).to.be.greaterThan(0); expect(prepackagedRules.rules_updated).to.eql(0); expect(Object.keys(prepackagedRules)).to.eql([ @@ -74,12 +77,12 @@ export default ({ getService }: FtrProviderContext): void => { await waitFor( async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .expect(200); return body.rules_not_installed === 0; }, - `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`, + PREBUILT_RULES_STATUS_URL, log ); @@ -87,7 +90,7 @@ export default ({ getService }: FtrProviderContext): void => { await waitFor( async () => { const { body, status } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) + .put(PREBUILT_RULES_URL) .set('kbn-xsrf', 'true') .send(); if (status === 200) { @@ -95,11 +98,11 @@ export default ({ getService }: FtrProviderContext): void => { } return status === 200; }, - DETECTION_ENGINE_PREPACKAGED_URL, + PREBUILT_RULES_URL, log ); - const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + const prepackagedRules = responseBody as InstallPrebuiltRulesAndTimelinesResponse; expect(prepackagedRules.rules_installed).to.eql(0); expect(prepackagedRules.timelines_installed).to.eql(0); }); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts index 6c4f73285e64f..55183b48d78f9 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -60,7 +60,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should create a single rule without an input index', async () => { - const rule: CreateRulesSchema = { + const rule: RuleCreateProps = { name: 'Simple Rule Query', description: 'Simple Rule Query', enabled: true, diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts b/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts index c3b2eb3568ccb..ce75086a2deea 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/get_prepackaged_rules_status.ts @@ -7,10 +7,12 @@ import expect from '@kbn/expect'; +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { - DETECTION_ENGINE_PREPACKAGED_URL, - DETECTION_ENGINE_RULES_URL, -} from '@kbn/security-solution-plugin/common/constants'; + PREBUILT_RULES_STATUS_URL, + PREBUILT_RULES_URL, +} from '@kbn/security-solution-plugin/common/detection_engine/prebuilt_rules'; + import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -40,7 +42,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return expected JSON keys of the pre-packaged rules and pre-packaged timelines status', async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -58,7 +60,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return that rules_not_installed are greater than zero', async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -67,7 +69,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return that timelines_not_installed are greater than zero', async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -76,7 +78,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return that rules_custom_installed, rules_installed, and rules_not_updated are zero', async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -87,7 +89,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return that timelines_installed, and timelines_not_updated are zero', async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -103,7 +105,7 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -115,14 +117,10 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should show rules and timelines are installed when adding pre-packaged rules', async () => { - await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + await supertest.put(PREBUILT_RULES_URL).set('kbn-xsrf', 'true').send().expect(200); const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/common/config.ts b/x-pack/test/detection_engine_api_integration/common/config.ts index a95cb937d4cd9..fbbe7fd62a7a8 100644 --- a/x-pack/test/detection_engine_api_integration/common/config.ts +++ b/x-pack/test/detection_engine_api_integration/common/config.ts @@ -77,6 +77,7 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'previewTelemetryUrlEnabled', ])}`, + '--xpack.task_manager.poll_interval=1000', ...(ssl ? [ `--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_actions.ts index 90c80c172fcfa..94a8b58ff70a0 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_actions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_actions.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; -import { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -86,7 +86,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); // create a rule with the action attached and a meta field - const ruleWithAction: CreateRulesSchema = { + const ruleWithAction: RuleCreateProps = { ...getRuleWithWebHookAction(hookAction.id, true), meta: {}, }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_prepackaged_rules.ts index 512863e039e3a..f90b1f8c8949b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/add_prepackaged_rules.ts @@ -6,9 +6,12 @@ */ import expect from '@kbn/expect'; -import { PrePackagedRulesAndTimelinesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response'; +import { + PREBUILT_RULES_STATUS_URL, + PREBUILT_RULES_URL, + InstallPrebuiltRulesAndTimelinesResponse, +} from '@kbn/security-solution-plugin/common/detection_engine/prebuilt_rules'; -import { DETECTION_ENGINE_PREPACKAGED_URL } from '@kbn/security-solution-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -42,7 +45,7 @@ export default ({ getService }: FtrProviderContext): void => { await waitFor( async () => { const { body, status } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) + .put(PREBUILT_RULES_URL) .set('kbn-xsrf', 'true') .send(); if (status === 200) { @@ -50,11 +53,11 @@ export default ({ getService }: FtrProviderContext): void => { } return status === 200; }, - DETECTION_ENGINE_PREPACKAGED_URL, + PREBUILT_RULES_URL, log ); - const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + const prepackagedRules = responseBody as InstallPrebuiltRulesAndTimelinesResponse; expect(prepackagedRules.rules_installed).to.be.greaterThan(0); expect(prepackagedRules.rules_updated).to.eql(0); expect(Object.keys(prepackagedRules)).to.eql([ @@ -73,12 +76,12 @@ export default ({ getService }: FtrProviderContext): void => { await waitFor( async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .expect(200); return body.rules_not_installed === 0; }, - `${DETECTION_ENGINE_PREPACKAGED_URL}/_status`, + PREBUILT_RULES_STATUS_URL, log ); @@ -86,7 +89,7 @@ export default ({ getService }: FtrProviderContext): void => { await waitFor( async () => { const { body, status } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) + .put(PREBUILT_RULES_URL) .set('kbn-xsrf', 'true') .send(); if (status === 200) { @@ -94,11 +97,11 @@ export default ({ getService }: FtrProviderContext): void => { } return status === 200; }, - DETECTION_ENGINE_PREPACKAGED_URL, + PREBUILT_RULES_URL, log ); - const prepackagedRules = responseBody as PrePackagedRulesAndTimelinesSchema; + const prepackagedRules = responseBody as InstallPrebuiltRulesAndTimelinesResponse; expect(prepackagedRules.rules_installed).to.eql(0); expect(prepackagedRules.timelines_installed).to.eql(0); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts index d208b522a1d44..0b7de71969a7b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/check_privileges.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; -import { ThresholdCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { ThresholdRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -86,7 +86,7 @@ export default ({ getService }: FtrProviderContext) => { }); it(`for threshold rule with index param: ${index}`, async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getThresholdRuleForSignalTesting(index), threshold: { field: [], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_new_terms.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_new_terms.ts index 7c97505307cd0..095ce3766918d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_new_terms.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_new_terms.ts @@ -5,78 +5,26 @@ * 2.0. */ -import { orderBy } from 'lodash'; import expect from '@kbn/expect'; -import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; -import { NewTermsCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request/rule_schemas.mock'; -import { DetectionAlert } from '@kbn/security-solution-plugin/common/detection_engine/schemas/alerts'; +import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema/mocks'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { - createRule, - createRuleWithExceptionEntries, - createSignalsIndex, - deleteAllAlerts, - deleteSignalsIndex, - getOpenSignals, - getSignalsByIds, - waitForRuleSuccessOrStatus, - waitForSignalsToBePresent, -} from '../../utils'; +import { deleteAllAlerts } from '../../utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { - const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); const log = getService('log'); - const es = getService('es'); /** * Specific api integration tests for threat matching rule type */ describe('create_new_terms', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await createSignalsIndex(supertest, log); - }); - afterEach(async () => { - await deleteSignalsIndex(supertest, log); await deleteAllAlerts(supertest, log); }); - it('should create a single rule with a rule_id and validate it ran successfully', async () => { - const ruleResponse = await createRule( - supertest, - log, - getCreateNewTermsRulesSchemaMock('rule-1', true) - ); - - await waitForRuleSuccessOrStatus( - supertest, - log, - ruleResponse.id, - RuleExecutionStatus.succeeded - ); - - const { body: rule } = await supertest - .get(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .query({ id: ruleResponse.id }) - .expect(200); - - expect(rule?.execution_summary?.last_execution.status).to.eql('succeeded'); - }); - it('should not be able to create a new terms rule with too small history window', async () => { const rule = { ...getCreateNewTermsRulesSchemaMock('rule-1'), @@ -92,379 +40,5 @@ export default ({ getService }: FtrProviderContext) => { "params invalid: History window size is smaller than rule interval + additional lookback, 'historyWindowStart' must be earlier than 'from'" ); }); - - const removeRandomValuedProperties = (alert: DetectionAlert | undefined) => { - if (!alert) { - return undefined; - } - const { - 'kibana.version': version, - 'kibana.alert.rule.execution.uuid': execUuid, - 'kibana.alert.rule.uuid': uuid, - '@timestamp': timestamp, - 'kibana.alert.rule.created_at': createdAt, - 'kibana.alert.rule.updated_at': updatedAt, - 'kibana.alert.uuid': alertUuid, - ...restOfAlert - } = alert; - return restOfAlert; - }; - - // This test also tests that alerts are NOT created for terms that are not new: the host name - // suricata-sensor-san-francisco appears in a document at 2019-02-19T20:42:08.230Z, but also appears - // in earlier documents so is not new. An alert should not be generated for that term. - it('should generate 1 alert with 1 selected field', async () => { - const rule: NewTermsCreateSchema = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['host.name'], - from: '2019-02-19T20:42:00.000Z', - history_window_start: '2019-01-19T20:42:00.000Z', - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(1); - expect(removeRandomValuedProperties(signalsOpen.hits.hits[0]._source)).eql({ - 'kibana.alert.new_terms': ['zeek-newyork-sha-aa8df15'], - 'kibana.alert.rule.category': 'New Terms Rule', - 'kibana.alert.rule.consumer': 'siem', - 'kibana.alert.rule.name': 'Query with a rule id', - 'kibana.alert.rule.producer': 'siem', - 'kibana.alert.rule.rule_type_id': 'siem.newTermsRule', - 'kibana.space_ids': ['default'], - 'kibana.alert.rule.tags': [], - agent: { - ephemeral_id: '7cc2091a-72f1-4c63-843b-fdeb622f9c69', - hostname: 'zeek-newyork-sha-aa8df15', - id: '4b4462ef-93d2-409c-87a6-299d942e5047', - type: 'auditbeat', - version: '8.0.0', - }, - cloud: { instance: { id: '139865230' }, provider: 'digitalocean', region: 'nyc1' }, - ecs: { version: '1.0.0-beta2' }, - host: { - architecture: 'x86_64', - hostname: 'zeek-newyork-sha-aa8df15', - id: '3729d06ce9964aa98549f41cbd99334d', - ip: ['157.230.208.30', '10.10.0.6', 'fe80::24ce:f7ff:fede:a571'], - mac: ['26:ce:f7:de:a5:71'], - name: 'zeek-newyork-sha-aa8df15', - os: { - codename: 'cosmic', - family: 'debian', - kernel: '4.18.0-10-generic', - name: 'Ubuntu', - platform: 'ubuntu', - version: '18.10 (Cosmic Cuttlefish)', - }, - }, - message: - 'Login by user root (UID: 0) on pts/0 (PID: 20638) from 8.42.77.171 (IP: 8.42.77.171)', - process: { pid: 20638 }, - service: { type: 'system' }, - source: { ip: '8.42.77.171' }, - user: { id: 0, name: 'root', terminal: 'pts/0' }, - 'event.action': 'user_login', - 'event.category': 'authentication', - 'event.dataset': 'login', - 'event.kind': 'signal', - 'event.module': 'system', - 'event.origin': '/var/log/wtmp', - 'event.outcome': 'success', - 'event.type': 'authentication_success', - 'kibana.alert.original_time': '2019-02-19T20:42:08.230Z', - 'kibana.alert.ancestors': [ - { - id: 'x07wJ2oB9v5HJNSHhyxi', - type: 'event', - index: 'auditbeat-8.0.0-2019.02.19-000001', - depth: 0, - }, - ], - 'kibana.alert.status': 'active', - 'kibana.alert.workflow_status': 'open', - 'kibana.alert.depth': 1, - 'kibana.alert.reason': - 'authentication event with source 8.42.77.171 by root on zeek-newyork-sha-aa8df15 created high alert Query with a rule id.', - 'kibana.alert.severity': 'high', - 'kibana.alert.risk_score': 55, - 'kibana.alert.rule.parameters': { - description: 'Detecting root and admin users', - risk_score: 55, - severity: 'high', - author: [], - false_positives: [], - from: '2019-02-19T20:42:00.000Z', - rule_id: 'rule-1', - max_signals: 100, - risk_score_mapping: [], - severity_mapping: [], - threat: [], - to: 'now', - references: [], - version: 1, - exceptions_list: [], - immutable: false, - related_integrations: [], - required_fields: [], - setup: '', - type: 'new_terms', - query: '*', - new_terms_fields: ['host.name'], - history_window_start: '2019-01-19T20:42:00.000Z', - index: ['auditbeat-*'], - language: 'kuery', - }, - 'kibana.alert.rule.actions': [], - 'kibana.alert.rule.author': [], - 'kibana.alert.rule.created_by': 'elastic', - 'kibana.alert.rule.description': 'Detecting root and admin users', - 'kibana.alert.rule.enabled': true, - 'kibana.alert.rule.exceptions_list': [], - 'kibana.alert.rule.false_positives': [], - 'kibana.alert.rule.from': '2019-02-19T20:42:00.000Z', - 'kibana.alert.rule.immutable': false, - 'kibana.alert.rule.indices': ['auditbeat-*'], - 'kibana.alert.rule.interval': '5m', - 'kibana.alert.rule.max_signals': 100, - 'kibana.alert.rule.references': [], - 'kibana.alert.rule.risk_score_mapping': [], - 'kibana.alert.rule.rule_id': 'rule-1', - 'kibana.alert.rule.severity_mapping': [], - 'kibana.alert.rule.threat': [], - 'kibana.alert.rule.to': 'now', - 'kibana.alert.rule.type': 'new_terms', - 'kibana.alert.rule.updated_by': 'elastic', - 'kibana.alert.rule.version': 1, - 'kibana.alert.rule.risk_score': 55, - 'kibana.alert.rule.severity': 'high', - 'kibana.alert.original_event.action': 'user_login', - 'kibana.alert.original_event.category': 'authentication', - 'kibana.alert.original_event.dataset': 'login', - 'kibana.alert.original_event.kind': 'event', - 'kibana.alert.original_event.module': 'system', - 'kibana.alert.original_event.origin': '/var/log/wtmp', - 'kibana.alert.original_event.outcome': 'success', - 'kibana.alert.original_event.type': 'authentication_success', - }); - }); - - it('should generate 3 alerts when 1 document has 3 new values', async () => { - const rule: NewTermsCreateSchema = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['host.ip'], - from: '2019-02-19T20:42:00.000Z', - history_window_start: '2019-01-19T20:42:00.000Z', - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(3); - const signalsOrderedByHostIp = orderBy( - signalsOpen.hits.hits, - '_source.kibana.alert.new_terms', - 'asc' - ); - expect(signalsOrderedByHostIp[0]._source?.['kibana.alert.new_terms']).eql(['10.10.0.6']); - expect(signalsOrderedByHostIp[1]._source?.['kibana.alert.new_terms']).eql(['157.230.208.30']); - expect(signalsOrderedByHostIp[2]._source?.['kibana.alert.new_terms']).eql([ - 'fe80::24ce:f7ff:fede:a571', - ]); - }); - - it('should generate alerts for every term when history window is small', async () => { - const rule: NewTermsCreateSchema = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['host.name'], - from: '2019-02-19T20:42:00.000Z', - // Set the history_window_start close to 'from' so we should alert on all terms in the time range - history_window_start: '2019-02-19T20:41:59.000Z', - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(5); - const hostNames = signalsOpen.hits.hits - .map((signal) => signal._source?.['kibana.alert.new_terms']) - .sort(); - expect(hostNames[0]).eql(['suricata-sensor-amsterdam']); - expect(hostNames[1]).eql(['suricata-sensor-san-francisco']); - expect(hostNames[2]).eql(['zeek-newyork-sha-aa8df15']); - expect(hostNames[3]).eql(['zeek-sensor-amsterdam']); - expect(hostNames[4]).eql(['zeek-sensor-san-francisco']); - }); - - describe('timestamp override and fallback', () => { - before(async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' - ); - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/timestamp_override_3' - ); - }); - after(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' - ); - await esArchiver.unload( - 'x-pack/test/functional/es_archives/security_solution/timestamp_override_3' - ); - }); - - it('should generate the correct alerts', async () => { - const rule: NewTermsCreateSchema = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - // myfakeindex-3 does not have event.ingested mapped so we can test if the runtime field - // 'kibana.combined_timestamp' handles unmapped fields properly - index: ['timestamp-fallback-test', 'myfakeindex-3'], - new_terms_fields: ['host.name'], - from: '2020-12-16T16:00:00.000Z', - // Set the history_window_start close to 'from' so we should alert on all terms in the time range - history_window_start: '2020-12-16T15:59:00.000Z', - timestamp_override: 'event.ingested', - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForSignalsToBePresent(supertest, log, 2, [createdRule.id]); - - const signalsOpen = await getSignalsByIds(supertest, log, [createdRule.id]); - expect(signalsOpen.hits.hits.length).eql(2); - const hostNames = signalsOpen.hits.hits - .map((signal) => signal._source?.['kibana.alert.new_terms']) - .sort(); - expect(hostNames[0]).eql(['host-3']); - expect(hostNames[1]).eql(['host-4']); - }); - }); - - it('should apply exceptions', async () => { - const rule: NewTermsCreateSchema = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['host.name'], - from: '2019-02-19T20:42:00.000Z', - // Set the history_window_start close to 'from' so we should alert on all terms in the time range - history_window_start: '2019-02-19T20:41:59.000Z', - }; - const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [ - [ - { - field: 'host.name', - operator: 'included', - type: 'match', - value: 'zeek-sensor-san-francisco', - }, - ], - ]); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(4); - const hostNames = signalsOpen.hits.hits - .map((signal) => signal._source?.['kibana.alert.new_terms']) - .sort(); - expect(hostNames[0]).eql(['suricata-sensor-amsterdam']); - expect(hostNames[1]).eql(['suricata-sensor-san-francisco']); - expect(hostNames[2]).eql(['zeek-newyork-sha-aa8df15']); - expect(hostNames[3]).eql(['zeek-sensor-amsterdam']); - }); - - it('should work for max signals > 100', async () => { - const maxSignals = 200; - const rule: NewTermsCreateSchema = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['process.pid'], - from: '2018-02-19T20:42:00.000Z', - // Set the history_window_start close to 'from' so we should alert on all terms in the time range - history_window_start: '2018-02-19T20:41:59.000Z', - max_signals: maxSignals, - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals( - supertest, - log, - es, - createdRule, - RuleExecutionStatus.succeeded, - maxSignals - ); - expect(signalsOpen.hits.hits.length).eql(maxSignals); - const processPids = signalsOpen.hits.hits - .map((signal) => signal._source?.['kibana.alert.new_terms']) - .sort(); - expect(processPids[0]).eql([1]); - }); - - describe('alerts should be be enriched', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - it('should be enriched with host risk score', async () => { - const rule: NewTermsCreateSchema = { - ...getCreateNewTermsRulesSchemaMock('rule-1', true), - new_terms_fields: ['host.name'], - from: '2019-02-19T20:42:00.000Z', - history_window_start: '2019-01-19T20:42:00.000Z', - }; - - const createdRule = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus( - supertest, - log, - createdRule.id, - RuleExecutionStatus.succeeded - ); - - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_level).to.eql('Low'); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_score_norm).to.eql(23); - }); - }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts index 082d8afab9dd1..a1da5d5697b1e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; -import { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; @@ -161,7 +161,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should create a single rule without an input index', async () => { - const rule: CreateRulesSchema = { + const rule: RuleCreateProps = { name: 'Simple Rule Query', description: 'Simple Rule Query', enabled: true, @@ -273,7 +273,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not create a rule if trying to add more than one default rule exception list', async () => { - const rule: CreateRulesSchema = { + const rule: RuleCreateProps = { name: 'Simple Rule Query', description: 'Simple Rule Query', enabled: true, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_threat_matching.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_threat_matching.ts deleted file mode 100644 index 906cc94f224d8..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_threat_matching.ts +++ /dev/null @@ -1,1420 +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 { get, isEqual } from 'lodash'; -import expect from '@kbn/expect'; -import { - ALERT_REASON, - ALERT_RULE_UUID, - ALERT_STATUS, - ALERT_RULE_NAMESPACE, - ALERT_RULE_UPDATED_AT, - ALERT_UUID, - ALERT_WORKFLOW_STATUS, - SPACE_IDS, - VERSION, -} from '@kbn/rule-data-utils'; -import { flattenWithPrefix } from '@kbn/securitysolution-rules'; - -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; -import { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; - -import { getCreateThreatMatchRulesSchemaMock } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request/rule_schemas.mock'; -import { getThreatMatchingSchemaPartialMock } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response/rules_schema.mocks'; -import { ENRICHMENT_TYPES } from '@kbn/security-solution-plugin/common/cti/constants'; -import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; -import { - ALERT_ANCESTORS, - ALERT_DEPTH, - ALERT_ORIGINAL_EVENT_ACTION, - ALERT_ORIGINAL_EVENT_CATEGORY, - ALERT_ORIGINAL_EVENT_MODULE, - ALERT_ORIGINAL_TIME, -} from '@kbn/security-solution-plugin/common/field_maps/field_names'; -import { - createRule, - createSignalsIndex, - deleteAllAlerts, - deleteSignalsIndex, - getSignalsByIds, - removeServerGeneratedProperties, - waitForRuleSuccessOrStatus, - waitForSignalsToBePresent, -} from '../../utils'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -const format = (value: unknown): string => JSON.stringify(value, null, 2); - -// Asserts that each expected value is included in the subject, independent of -// ordering. Uses _.isEqual for value comparison. -const assertContains = (subject: unknown[], expected: unknown[]) => - expected.forEach((expectedValue) => - expect(subject.some((value) => isEqual(value, expectedValue))).to.eql( - true, - `expected ${format(subject)} to contain ${format(expectedValue)}` - ) - ); - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext) => { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - const log = getService('log'); - - /** - * Specific api integration tests for threat matching rule type - */ - describe('create_threat_matching', () => { - describe('creating threat match rule', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - }); - - it('should create a single rule with a rule_id', async () => { - const ruleResponse = await createRule( - supertest, - log, - getCreateThreatMatchRulesSchemaMock() - ); - const bodyToCompare = removeServerGeneratedProperties(ruleResponse); - expect(bodyToCompare).to.eql(getThreatMatchingSchemaPartialMock()); - }); - - it('should create a single rule with a rule_id and validate it ran successfully', async () => { - const ruleResponse = await createRule( - supertest, - log, - getCreateThreatMatchRulesSchemaMock('rule-1', true) - ); - - await waitForRuleSuccessOrStatus( - supertest, - log, - ruleResponse.id, - RuleExecutionStatus.succeeded - ); - - const { body: rule } = await supertest - .get(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .query({ id: ruleResponse.id }) - .expect(200); - - const bodyToCompare = removeServerGeneratedProperties(ruleResponse); - expect(bodyToCompare).to.eql(getThreatMatchingSchemaPartialMock(true)); - - // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe - expect(rule?.execution_summary?.last_execution.status).to.eql('succeeded'); - }); - }); - - describe('tests with auditbeat data', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await deleteAllAlerts(supertest, log); - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - }); - - it('should be able to execute and get 10 signals when doing a specific query', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - // We match host.name against host.name - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const createdRule = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, createdRule.id); - await waitForSignalsToBePresent(supertest, log, 10, [createdRule.id]); - const signalsOpen = await getSignalsByIds(supertest, log, [createdRule.id]); - expect(signalsOpen.hits.hits.length).equal(10); - const fullSource = signalsOpen.hits.hits.find( - (signal) => - (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' - ); - const fullSignal = fullSource?._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - expect(fullSignal).eql({ - ...fullSignal, - '@timestamp': fullSignal['@timestamp'], - agent: { - ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', - hostname: 'zeek-sensor-amsterdam', - id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', - type: 'auditbeat', - version: '8.0.0', - }, - auditd: { - data: { - hostname: '46.101.47.213', - op: 'PAM:bad_ident', - terminal: 'ssh', - }, - message_type: 'user_err', - result: 'fail', - sequence: 2267, - session: 'unset', - summary: { - actor: { - primary: 'unset', - secondary: 'root', - }, - how: '/usr/sbin/sshd', - object: { - primary: 'ssh', - secondary: '46.101.47.213', - type: 'user-session', - }, - }, - }, - cloud: { - instance: { - id: '133551048', - }, - provider: 'digitalocean', - region: 'ams3', - }, - ecs: { - version: '1.0.0-beta2', - }, - ...flattenWithPrefix('event', { - action: 'error', - category: 'user-login', - module: 'auditd', - kind: 'signal', - }), - host: { - architecture: 'x86_64', - containerized: false, - hostname: 'zeek-sensor-amsterdam', - id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', - name: 'zeek-sensor-amsterdam', - os: { - codename: 'bionic', - family: 'debian', - kernel: '4.15.0-45-generic', - name: 'Ubuntu', - platform: 'ubuntu', - version: '18.04.2 LTS (Bionic Beaver)', - }, - }, - network: { - direction: 'incoming', - }, - process: { - executable: '/usr/sbin/sshd', - pid: 32739, - }, - service: { - type: 'auditd', - }, - source: { - ip: '46.101.47.213', - }, - user: { - audit: { - id: 'unset', - }, - id: '0', - name: 'root', - }, - [ALERT_ANCESTORS]: [ - { - id: '7yJ-B2kBR346wHgnhlMn', - type: 'event', - index: 'auditbeat-8.0.0-2019.02.19-000001', - depth: 0, - }, - ], - [ALERT_DEPTH]: 1, - [ALERT_ORIGINAL_EVENT_ACTION]: 'error', - [ALERT_ORIGINAL_EVENT_CATEGORY]: 'user-login', - [ALERT_ORIGINAL_EVENT_MODULE]: 'auditd', - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_REASON]: - 'user-login event with source 46.101.47.213 by root on zeek-sensor-amsterdam created high alert Query with a rule id.', - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_STATUS]: 'active', - [ALERT_UUID]: fullSignal[ALERT_UUID], - [ALERT_WORKFLOW_STATUS]: 'open', - [SPACE_IDS]: ['default'], - [VERSION]: fullSignal[VERSION], - threat: { - enrichments: get(fullSignal, 'threat.enrichments'), - }, - ...flattenWithPrefix(ALERT_RULE_NAMESPACE, { - actions: [], - author: [], - category: 'Indicator Match Rule', - consumer: 'siem', - created_at: createdRule.created_at, - created_by: 'elastic', - description: 'Detecting root and admin users', - enabled: true, - exceptions_list: [], - false_positives: [], - from: '1900-01-01T00:00:00.000Z', - immutable: false, - interval: '5m', - max_signals: 100, - name: 'Query with a rule id', - producer: 'siem', - references: [], - risk_score: 55, - risk_score_mapping: [], - rule_id: createdRule.rule_id, - rule_type_id: 'siem.indicatorRule', - severity: 'high', - severity_mapping: [], - tags: [], - threat: [], - to: 'now', - type: 'threat_match', - updated_at: fullSignal[ALERT_RULE_UPDATED_AT], - updated_by: 'elastic', - uuid: createdRule.id, - version: 1, - }), - }); - }); - - it('should return 0 matches if the mapping does not match against anything in the mapping', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - // We match host.name against host.name - { - entries: [ - { - field: 'host.name', - value: 'invalid.mapping.value', // invalid mapping value - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const ruleResponse = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, ruleResponse.id); - const signalsOpen = await getSignalsByIds(supertest, log, [ruleResponse.id]); - expect(signalsOpen.hits.hits.length).equal(0); - }); - - it('should return 0 signals when using an AND and one of the clauses does not have data', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - { - entries: [ - { - field: 'source.ip', - value: 'source.ip', - type: 'mapping', - }, - { - field: 'source.ip', - value: 'destination.ip', // All records from the threat query do NOT have destination.ip, so those records that do not should drop this entire AND clause. - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const ruleResponse = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, ruleResponse.id); - const signalsOpen = await getSignalsByIds(supertest, log, [ruleResponse.id]); - expect(signalsOpen.hits.hits.length).equal(0); - }); - - it('should return 0 signals when using an AND and one of the clauses has a made up value that does not exist', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - type: 'threat_match', - index: ['auditbeat-*'], - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - { - entries: [ - { - field: 'source.ip', - value: 'source.ip', - type: 'mapping', - }, - { - field: 'source.ip', - value: 'made.up.non.existent.field', // made up field should not match - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const ruleResponse = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, ruleResponse.id); - const signalsOpen = await getSignalsByIds(supertest, log, [ruleResponse.id]); - expect(signalsOpen.hits.hits.length).equal(0); - }); - - describe('timeout behavior', () => { - // Flaky - it.skip('will return an error if a rule execution exceeds the rule interval', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a short interval', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: '*:*', // broad query to take more time - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - concurrent_searches: 1, - interval: '1s', // short interval - items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id, RuleExecutionStatus.failed); - - const { body } = await supertest - .post(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .query({ id }) - .expect(200); - - // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe - expect(body?.execution_summary?.last_execution.message).to.contain( - 'execution has exceeded its allotted interval' - ); - }); - }); - - describe('indicator enrichment: threat-first search', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/filebeat/threat_intel'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/threat_intel'); - }); - - it('enriches signals with the single indicator that matched', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', // narrow events down to 2 with a destination.ip - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.domain', - field: 'destination.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(2); - - const { hits } = signalsOpen.hits; - const threats = hits.map((hit) => hit._source?.threat); - expect(threats).to.eql([ - { - enrichments: [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - type: 'url', - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ], - }, - { - enrichments: [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - type: 'url', - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ], - }, - ]); - }); - - it('enriches signals with multiple indicators if several matched', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.ip: *', - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(1); - - const { hits } = signalsOpen.hits; - const [threat] = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threat.enrichments, [ - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - provider: 'other_provider', - type: 'ip', - }, - - matched: { - atomic: '45.115.45.3', - id: '978787', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - - it('adds a single indicator that matched multiple fields', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.port: 57324 or threat.indicator.ip:45.115.45.3', // narrow our query to a single indicator - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.port', - field: 'source.port', - type: 'mapping', - }, - ], - }, - { - entries: [ - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(1); - - const { hits } = signalsOpen.hits; - const [threat] = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threat.enrichments, [ - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - // We do not merge matched indicators during enrichment, so in - // certain circumstances a given indicator document could appear - // multiple times in an enriched alert (albeit with different - // threat.indicator.matched data). That's the case with the - // first and third indicators matched, here. - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - - matched: { - atomic: 57324, - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.port', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - provider: 'other_provider', - type: 'ip', - }, - matched: { - atomic: '45.115.45.3', - id: '978787', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - - it('generates multiple signals with multiple matches', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - threat_language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', - threat_query: '*:*', - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.port', - field: 'source.port', - type: 'mapping', - }, - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - { - entries: [ - { - value: 'threat.indicator.domain', - field: 'destination.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(2); - - const { hits } = signalsOpen.hits; - const threats = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threats[0].enrichments, [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - type: 'url', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: 57324, - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.port', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - - assertContains(threats[1].enrichments, [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - type: 'url', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - }); - - describe('indicator enrichment: event-first search', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/filebeat/threat_intel'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/threat_intel'); - }); - - it('enriches signals with the single indicator that matched', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'destination.ip:159.89.119.67', - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.domain', - field: 'destination.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(2); - - const { hits } = signalsOpen.hits; - const threats = hits.map((hit) => hit._source?.threat); - expect(threats).to.eql([ - { - enrichments: [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - type: 'url', - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ], - }, - { - enrichments: [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - type: 'url', - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ], - }, - ]); - }); - - it('enriches signals with multiple indicators if several matched', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'source.port: 57324', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.ip: *', - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(1); - - const { hits } = signalsOpen.hits; - const [threat] = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threat.enrichments, [ - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - provider: 'other_provider', - type: 'ip', - }, - - matched: { - atomic: '45.115.45.3', - id: '978787', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - - it('adds a single indicator that matched multiple fields', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: 'source.port: 57324', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', - threat_query: 'threat.indicator.ip: *', - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.port', - field: 'source.port', - type: 'mapping', - }, - ], - }, - { - entries: [ - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(1); - - const { hits } = signalsOpen.hits; - const [threat] = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threat.enrichments, [ - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - // We do not merge matched indicators during enrichment, so in - // certain circumstances a given indicator document could appear - // multiple times in an enriched alert (albeit with different - // threat.indicator.matched data). That's the case with the - // first and third indicators matched, here. - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - - matched: { - atomic: 57324, - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.port', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - provider: 'other_provider', - type: 'ip', - }, - matched: { - atomic: '45.115.45.3', - id: '978787', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - - it('generates multiple signals with multiple matches', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - threat_language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '(source.port:57324 and source.ip:45.115.45.3) or destination.ip:159.89.119.67', // narrow our query to a single record that matches two indicators - threat_indicator_path: 'threat.indicator', - threat_query: '*:*', - threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module - threat_mapping: [ - { - entries: [ - { - value: 'threat.indicator.port', - field: 'source.port', - type: 'mapping', - }, - { - value: 'threat.indicator.ip', - field: 'source.ip', - type: 'mapping', - }, - ], - }, - { - entries: [ - { - value: 'threat.indicator.domain', - field: 'destination.ip', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).equal(2); - - const { hits } = signalsOpen.hits; - const threats = hits.map((hit) => hit._source?.threat) as Array<{ - enrichments: unknown[]; - }>; - - assertContains(threats[0].enrichments, [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - type: 'url', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: '45.115.45.3', - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - { - feed: {}, - indicator: { - description: 'this should match auditbeat/hosts on both port and ip', - first_seen: '2021-01-26T11:06:03.000Z', - ip: '45.115.45.3', - port: 57324, - provider: 'geenensp', - type: 'url', - }, - matched: { - atomic: 57324, - id: '978785', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'source.port', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - - assertContains(threats[1].enrichments, [ - { - feed: {}, - indicator: { - description: "domain should match the auditbeat hosts' data's source.ip", - domain: '159.89.119.67', - first_seen: '2021-01-26T11:09:04.000Z', - provider: 'geenensp', - type: 'url', - url: { - full: 'http://159.89.119.67:59600/bin.sh', - scheme: 'http', - }, - }, - matched: { - atomic: '159.89.119.67', - id: '978783', - index: 'filebeat-8.0.0-2021.01.26-000001', - field: 'destination.ip', - type: ENRICHMENT_TYPES.IndicatorMatchRule, - }, - }, - ]); - }); - }); - - describe('alerts should be be enriched', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - it('should be enriched with host risk score', async () => { - const rule: CreateRulesSchema = { - description: 'Detecting root and admin users', - name: 'Query with a rule id', - severity: 'high', - index: ['auditbeat-*'], - type: 'threat_match', - risk_score: 55, - language: 'kuery', - rule_id: 'rule-1', - from: '1900-01-01T00:00:00.000Z', - query: '*:*', - threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip - threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity - threat_mapping: [ - // We match host.name against host.name - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [], - }; - - const createdRule = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, createdRule.id); - await waitForSignalsToBePresent(supertest, log, 10, [createdRule.id]); - const signalsOpen = await getSignalsByIds(supertest, log, [createdRule.id]); - expect(signalsOpen.hits.hits.length).equal(10); - const fullSource = signalsOpen.hits.hits.find( - (signal) => - (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' - ); - const fullSignal = fullSource?._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - - expect(fullSignal?.host?.risk?.calculated_level).to.eql('Critical'); - expect(fullSignal?.host?.risk?.calculated_score_norm).to.eql(70); - }); - }); - }); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rule_exception_references.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rule_exception_references.ts index 173aaa86c4328..6ddc5dafaafa1 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rule_exception_references.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rule_exception_references.ts @@ -9,13 +9,17 @@ import expect from '@kbn/expect'; -import { DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL } from '@kbn/security-solution-plugin/common/constants'; import { CreateExceptionListSchema, ExceptionListTypeEnum, } from '@kbn/securitysolution-io-ts-list-types'; + import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; -import { RuleReferencesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response'; +import { + DETECTION_ENGINE_RULES_EXCEPTIONS_REFERENCE_URL, + RuleReferencesSchema, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_exceptions'; + import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/generating_signals.ts deleted file mode 100644 index b71cd23063550..0000000000000 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/generating_signals.ts +++ /dev/null @@ -1,1554 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { - ALERT_REASON, - ALERT_RISK_SCORE, - ALERT_RULE_NAME, - ALERT_RULE_PARAMETERS, - ALERT_RULE_RULE_ID, - ALERT_RULE_RULE_NAME_OVERRIDE, - ALERT_RULE_UUID, - ALERT_SEVERITY, - ALERT_WORKFLOW_STATUS, - EVENT_ACTION, - EVENT_KIND, -} from '@kbn/rule-data-utils'; -import { flattenWithPrefix } from '@kbn/securitysolution-rules'; - -import { orderBy, get } from 'lodash'; - -import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; -import { - EqlCreateSchema, - QueryCreateSchema, - SavedQueryCreateSchema, - ThresholdCreateSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; -import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; -import { - ALERT_ANCESTORS, - ALERT_DEPTH, - ALERT_ORIGINAL_TIME, - ALERT_ORIGINAL_EVENT, - ALERT_ORIGINAL_EVENT_CATEGORY, - ALERT_GROUP_ID, - ALERT_THRESHOLD_RESULT, -} from '@kbn/security-solution-plugin/common/field_maps/field_names'; -import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; -import { - createRule, - createSignalsIndex, - deleteAllAlerts, - deleteSignalsIndex, - getEqlRuleForSignalTesting, - getOpenSignals, - getRuleForSignalTesting, - getSignalsByIds, - getSignalsByRuleIds, - getSimpleRule, - getThresholdRuleForSignalTesting, - waitForRuleSuccessOrStatus, - waitForSignalsToBePresent, -} from '../../utils'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -/** - * Specific _id to use for some of the tests. If the archiver changes and you see errors - * here, update this to a new value of a chosen auditbeat record and update the tests values. - */ -export const ID = 'BhbXBmkBR346wHgn4PeZ'; - -// eslint-disable-next-line import/no-default-export -export default ({ getService }: FtrProviderContext) => { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const es = getService('es'); - const log = getService('log'); - - describe('Generating signals from source indexes', () => { - beforeEach(async () => { - await deleteSignalsIndex(supertest, log); - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - }); - - describe('Signals from audit beat are of the expected structure', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - it('should have the specific audit record for _id or none of these tests below will pass', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).greaterThan(0); - }); - - it('should abide by max_signals > 100', async () => { - const maxSignals = 500; - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['auditbeat-*']), - max_signals: maxSignals, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, maxSignals, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id], maxSignals); - expect(signalsOpen.hits.hits.length).equal(maxSignals); - }); - - it('should have recorded the rule_id within the signal', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits[0]._source![ALERT_RULE_RULE_ID]).eql(getSimpleRule().rule_id); - }); - - it('should query and get back expected signal structure using a basic KQL query', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - const signal = signalsOpen.hits.hits[0]._source!; - - expect(signal).eql({ - ...signal, - [ALERT_ANCESTORS]: [ - { - id: 'BhbXBmkBR346wHgn4PeZ', - type: 'event', - index: 'auditbeat-8.0.0-2019.02.19-000001', - depth: 0, - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 1, - [ALERT_ORIGINAL_TIME]: '2019-02-19T17:40:03.790Z', - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'socket_closed', - dataset: 'socket', - kind: 'event', - module: 'system', - }), - }); - }); - - it('should query and get back expected signal structure using a saved query rule', async () => { - const rule: SavedQueryCreateSchema = { - ...getRuleForSignalTesting(['auditbeat-*']), - type: 'saved_query', - query: `_id:${ID}`, - saved_id: 'doesnt-exist', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - const signal = signalsOpen.hits.hits[0]._source!; - expect(signal).eql({ - ...signal, - [ALERT_ANCESTORS]: [ - { - id: 'BhbXBmkBR346wHgn4PeZ', - type: 'event', - index: 'auditbeat-8.0.0-2019.02.19-000001', - depth: 0, - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 1, - [ALERT_ORIGINAL_TIME]: '2019-02-19T17:40:03.790Z', - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'socket_closed', - dataset: 'socket', - kind: 'event', - module: 'system', - }), - }); - }); - - it('should query and get back expected signal structure when it is a signal on a signal', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id: createdId } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, createdId); - await waitForSignalsToBePresent(supertest, log, 1, [createdId]); - - // Run signals on top of that 1 signal which should create a single signal (on top of) a signal - const ruleForSignals: QueryCreateSchema = { - ...getRuleForSignalTesting([`.alerts-security.alerts-default*`]), - rule_id: 'signal-on-signal', - }; - - const { id } = await createRule(supertest, log, ruleForSignals); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - - // Get our single signal on top of a signal - const signalsOpen = await getSignalsByRuleIds(supertest, log, ['signal-on-signal']); - - const signal = signalsOpen.hits.hits[0]._source!; - expect(signal).eql({ - ...signal, - [ALERT_ANCESTORS]: [ - { - id: 'BhbXBmkBR346wHgn4PeZ', - type: 'event', - index: 'auditbeat-8.0.0-2019.02.19-000001', - depth: 0, - }, - { - ...(signal[ALERT_ANCESTORS] as Ancestor[])[1], - type: 'signal', - index: '.internal.alerts-security.alerts-default-000001', - depth: 1, - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 2, - [ALERT_ORIGINAL_TIME]: signal[ALERT_ORIGINAL_TIME], // original_time will always be changing sine it's based on a signal created here, so skip testing it - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'socket_closed', - dataset: 'socket', - kind: 'signal', - module: 'system', - }), - }); - }); - - describe('EQL Rules', () => { - before(async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/timestamp_override_6' - ); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/security_solution/timestamp_override_6' - ); - }); - - it('generates a correctly formatted signal from EQL non-sequence queries', async () => { - const rule: EqlCreateSchema = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(1); - const fullSignal = signals.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - - expect(fullSignal).eql({ - ...fullSignal, - agent: { - ephemeral_id: '0010d67a-14f7-41da-be30-489fea735967', - hostname: 'suricata-zeek-sensor-toronto', - id: 'a1d7b39c-f898-4dbe-a761-efb61939302d', - type: 'auditbeat', - version: '8.0.0', - }, - auditd: { - data: { - audit_enabled: '1', - old: '1', - }, - message_type: 'config_change', - result: 'success', - sequence: 1496, - session: 'unset', - summary: { - actor: { - primary: 'unset', - }, - object: { - primary: '1', - type: 'audit-config', - }, - }, - }, - cloud: { - instance: { - id: '133555295', - }, - provider: 'digitalocean', - region: 'tor1', - }, - ecs: { - version: '1.0.0-beta2', - }, - ...flattenWithPrefix('event', { - action: 'changed-audit-configuration', - category: 'configuration', - module: 'auditd', - kind: 'signal', - }), - host: { - architecture: 'x86_64', - containerized: false, - hostname: 'suricata-zeek-sensor-toronto', - id: '8cc95778cce5407c809480e8e32ad76b', - name: 'suricata-zeek-sensor-toronto', - os: { - codename: 'bionic', - family: 'debian', - kernel: '4.15.0-45-generic', - name: 'Ubuntu', - platform: 'ubuntu', - version: '18.04.2 LTS (Bionic Beaver)', - }, - }, - service: { - type: 'auditd', - }, - user: { - audit: { - id: 'unset', - }, - }, - [ALERT_REASON]: - 'configuration event on suricata-zeek-sensor-toronto created high alert Signal Testing Query.', - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 1, - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: '9xbRBmkBR346wHgngz2D', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - ], - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'changed-audit-configuration', - category: 'configuration', - module: 'auditd', - }), - }); - }); - - it('generates up to max_signals for non-sequence EQL queries', async () => { - const rule: EqlCreateSchema = getEqlRuleForSignalTesting(['auditbeat-*']); - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 100, [id]); - const signals = await getSignalsByIds(supertest, log, [id], 1000); - const filteredSignals = signals.hits.hits.filter( - (signal) => signal._source?.[ALERT_DEPTH] === 1 - ); - expect(filteredSignals.length).eql(100); - }); - - it('uses the provided event_category_override', async () => { - const rule: EqlCreateSchema = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'config_change where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', - event_category_override: 'auditd.message_type', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(1); - const fullSignal = signals.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - - expect(fullSignal).eql({ - ...fullSignal, - auditd: { - data: { - audit_enabled: '1', - old: '1', - }, - message_type: 'config_change', - result: 'success', - sequence: 1496, - session: 'unset', - summary: { - actor: { - primary: 'unset', - }, - object: { - primary: '1', - type: 'audit-config', - }, - }, - }, - ...flattenWithPrefix('event', { - action: 'changed-audit-configuration', - category: 'configuration', - module: 'auditd', - kind: 'signal', - }), - service: { - type: 'auditd', - }, - user: { - audit: { - id: 'unset', - }, - }, - [ALERT_REASON]: - 'configuration event on suricata-zeek-sensor-toronto created high alert Signal Testing Query.', - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 1, - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: '9xbRBmkBR346wHgngz2D', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - ], - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'changed-audit-configuration', - category: 'configuration', - module: 'auditd', - }), - }); - }); - - it('uses the provided timestamp_field', async () => { - const rule: EqlCreateSchema = { - ...getEqlRuleForSignalTesting(['fake.index.1']), - query: 'any where true', - timestamp_field: 'created_at', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(3); - - const createdAtHits = signals.hits.hits.map((hit) => hit._source?.created_at); - expect(createdAtHits).to.eql([1622676785, 1622676790, 1622676795]); - }); - - it('uses the provided tiebreaker_field', async () => { - const rule: EqlCreateSchema = { - ...getEqlRuleForSignalTesting(['fake.index.1']), - query: 'any where true', - tiebreaker_field: 'locale', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(3); - - const createdAtHits = signals.hits.hits.map((hit) => hit._source?.locale); - expect(createdAtHits).to.eql(['es', 'pt', 'ua']); - }); - - it('generates building block signals from EQL sequences in the expected form', async () => { - const rule: EqlCreateSchema = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'sequence by host.name [anomoly where true] [any where true]', // TODO: spelling - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - const buildingBlock = signals.hits.hits.find( - (signal) => - signal._source?.[ALERT_DEPTH] === 1 && - get(signal._source, ALERT_ORIGINAL_EVENT_CATEGORY) === 'anomoly' - ); - expect(buildingBlock).not.eql(undefined); - const fullSignal = buildingBlock?._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - - expect(fullSignal).eql({ - ...fullSignal, - agent: { - ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', - hostname: 'zeek-sensor-amsterdam', - id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', - type: 'auditbeat', - version: '8.0.0', - }, - auditd: { - data: { - a0: '3', - a1: '107', - a2: '1', - a3: '7ffc186b58e0', - arch: 'x86_64', - auid: 'unset', - dev: 'eth0', - exit: '0', - gid: '0', - old_prom: '0', - prom: '256', - ses: 'unset', - syscall: 'setsockopt', - tty: '(none)', - uid: '0', - }, - message_type: 'anom_promiscuous', - result: 'success', - sequence: 1392, - session: 'unset', - summary: { - actor: { - primary: 'unset', - secondary: 'root', - }, - how: '/usr/bin/bro', - object: { - primary: 'eth0', - type: 'network-device', - }, - }, - }, - cloud: { instance: { id: '133551048' }, provider: 'digitalocean', region: 'ams3' }, - ecs: { version: '1.0.0-beta2' }, - ...flattenWithPrefix('event', { - action: 'changed-promiscuous-mode-on-device', - category: 'anomoly', - module: 'auditd', - kind: 'signal', - }), - host: { - architecture: 'x86_64', - containerized: false, - hostname: 'zeek-sensor-amsterdam', - id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', - name: 'zeek-sensor-amsterdam', - os: { - codename: 'bionic', - family: 'debian', - kernel: '4.15.0-45-generic', - name: 'Ubuntu', - platform: 'ubuntu', - version: '18.04.2 LTS (Bionic Beaver)', - }, - }, - process: { - executable: '/usr/bin/bro', - name: 'bro', - pid: 30157, - ppid: 30151, - title: - '/usr/bin/bro -i eth0 -U .status -p broctl -p broctl-live -p standalone -p local -p bro local.bro broctl broctl/standalone broctl', - }, - service: { type: 'auditd' }, - user: { - audit: { id: 'unset' }, - effective: { - group: { - id: '0', - name: 'root', - }, - id: '0', - name: 'root', - }, - filesystem: { - group: { - id: '0', - name: 'root', - }, - id: '0', - name: 'root', - }, - group: { id: '0', name: 'root' }, - id: '0', - name: 'root', - saved: { - group: { - id: '0', - name: 'root', - }, - id: '0', - name: 'root', - }, - }, - [ALERT_REASON]: - 'anomoly event with process bro, by root on zeek-sensor-amsterdam created high alert Signal Testing Query.', - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_GROUP_ID]: fullSignal[ALERT_GROUP_ID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 1, - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: 'VhXOBmkBR346wHgnLP8T', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - ], - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'changed-promiscuous-mode-on-device', - category: 'anomoly', - module: 'auditd', - }), - }); - }); - - it('generates shell signals from EQL sequences in the expected form', async () => { - const rule: EqlCreateSchema = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'sequence by host.name [anomoly where true] [any where true]', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 3, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - const sequenceSignal = signalsOpen.hits.hits.find( - (signal) => signal._source?.[ALERT_DEPTH] === 2 - ); - const source = sequenceSignal?._source; - if (!source) { - return expect(source).to.be.ok(); - } - const eventIds = (source?.[ALERT_ANCESTORS] as Ancestor[]) - .filter((event) => event.depth === 1) - .map((event) => event.id); - expect(source).eql({ - ...source, - agent: { - ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', - hostname: 'zeek-sensor-amsterdam', - id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', - type: 'auditbeat', - version: '8.0.0', - }, - auditd: { session: 'unset', summary: { actor: { primary: 'unset' } } }, - cloud: { instance: { id: '133551048' }, provider: 'digitalocean', region: 'ams3' }, - ecs: { version: '1.0.0-beta2' }, - [EVENT_KIND]: 'signal', - host: { - architecture: 'x86_64', - containerized: false, - hostname: 'zeek-sensor-amsterdam', - id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', - name: 'zeek-sensor-amsterdam', - os: { - codename: 'bionic', - family: 'debian', - kernel: '4.15.0-45-generic', - name: 'Ubuntu', - platform: 'ubuntu', - version: '18.04.2 LTS (Bionic Beaver)', - }, - }, - service: { type: 'auditd' }, - user: { audit: { id: 'unset' }, id: '0', name: 'root' }, - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_DEPTH]: 2, - [ALERT_GROUP_ID]: source[ALERT_GROUP_ID], - [ALERT_REASON]: - 'event by root on zeek-sensor-amsterdam created high alert Signal Testing Query.', - [ALERT_RULE_UUID]: source[ALERT_RULE_UUID], - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: 'VhXOBmkBR346wHgnLP8T', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - { - depth: 1, - id: eventIds[0], - index: '', - rule: source[ALERT_RULE_UUID], - type: 'signal', - }, - { - depth: 0, - id: '4hbXBmkBR346wHgn6fdp', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - { - depth: 1, - id: eventIds[1], - index: '', - rule: source[ALERT_RULE_UUID], - type: 'signal', - }, - ], - }); - }); - - it('generates up to max_signals with an EQL rule', async () => { - const rule: EqlCreateSchema = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'sequence by host.name [any where true] [any where true]', - max_signals: 200, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - // For EQL rules, max_signals is the maximum number of detected sequences: each sequence has a building block - // alert for each event in the sequence, so max_signals=200 results in 400 building blocks in addition to - // 200 regular alerts - await waitForSignalsToBePresent(supertest, log, 600, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id], 1000); - expect(signalsOpen.hits.hits.length).eql(600); - const shellSignals = signalsOpen.hits.hits.filter( - (signal) => signal._source?.[ALERT_DEPTH] === 2 - ); - const buildingBlocks = signalsOpen.hits.hits.filter( - (signal) => signal._source?.[ALERT_DEPTH] === 1 - ); - expect(shellSignals.length).eql(200); - expect(buildingBlocks.length).eql(400); - }); - - it('generates signals when an index name contains special characters to encode', async () => { - const rule: EqlCreateSchema = { - ...getEqlRuleForSignalTesting(['auditbeat-*', '<my-index-{now/d}*>']), - query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(1); - }); - - it('uses the provided filters', async () => { - const rule: EqlCreateSchema = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'any where true', - filters: [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'source.ip', - params: { - query: '46.148.18.163', - }, - }, - query: { - match_phrase: { - 'source.ip': '46.148.18.163', - }, - }, - }, - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'event.action', - params: { - query: 'error', - }, - }, - query: { - match_phrase: { - 'event.action': 'error', - }, - }, - }, - ], - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(2); - }); - - describe('EQL alerts should be be enriched', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - it('should be enriched with host risk score', async () => { - const rule: EqlCreateSchema = { - ...getEqlRuleForSignalTesting(['auditbeat-*']), - query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signals = await getSignalsByIds(supertest, log, [id]); - expect(signals.hits.hits.length).eql(1); - const fullSignal = signals.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - expect(fullSignal?.host?.risk?.calculated_level).to.eql('Critical'); - expect(fullSignal?.host?.risk?.calculated_score_norm).to.eql(96); - }); - }); - }); - - describe('Threshold Rules', () => { - it('generates 1 signal from Threshold rules when threshold is met', async () => { - const rule: ThresholdCreateSchema = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: ['host.id'], - value: 700, - }, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).eql(1); - const fullSignal = signalsOpen.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - const eventIds = (fullSignal?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); - expect(fullSignal).eql({ - ...fullSignal, - 'host.id': '8cc95778cce5407c809480e8e32ad76b', - [EVENT_KIND]: 'signal', - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: eventIds[0], - index: 'auditbeat-*', - type: 'event', - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_REASON]: 'event created high alert Signal Testing Query.', - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_DEPTH]: 1, - [ALERT_THRESHOLD_RESULT]: { - terms: [ - { - field: 'host.id', - value: '8cc95778cce5407c809480e8e32ad76b', - }, - ], - count: 788, - from: '2019-02-19T07:12:05.332Z', - }, - }); - }); - - it('generates 2 signals from Threshold rules when threshold is met', async () => { - const rule: ThresholdCreateSchema = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: 'host.id', - value: 100, - }, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).eql(2); - }); - - it('applies the provided query before bucketing ', async () => { - const rule: ThresholdCreateSchema = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - query: 'host.id:"2ab45fc1c41e4c84bbd02202a7e5761f"', - threshold: { - field: 'process.name', - value: 21, - }, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).eql(1); - }); - - it('generates no signals from Threshold rules when threshold is met and cardinality is not met', async () => { - const rule: ThresholdCreateSchema = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: 'host.id', - value: 100, - cardinality: [ - { - field: 'destination.ip', - value: 100, - }, - ], - }, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(0); - }); - - it('generates no signals from Threshold rules when cardinality is met and threshold is not met', async () => { - const rule: ThresholdCreateSchema = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: 'host.id', - value: 1000, - cardinality: [ - { - field: 'destination.ip', - value: 5, - }, - ], - }, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(0); - }); - - it('generates signals from Threshold rules when threshold and cardinality are both met', async () => { - const rule: ThresholdCreateSchema = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: 'host.id', - value: 100, - cardinality: [ - { - field: 'destination.ip', - value: 5, - }, - ], - }, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(1); - const fullSignal = signalsOpen.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - const eventIds = (fullSignal?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); - expect(fullSignal).eql({ - ...fullSignal, - 'host.id': '8cc95778cce5407c809480e8e32ad76b', - [EVENT_KIND]: 'signal', - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: eventIds[0], - index: 'auditbeat-*', - type: 'event', - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_REASON]: `event created high alert Signal Testing Query.`, - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_DEPTH]: 1, - [ALERT_THRESHOLD_RESULT]: { - terms: [ - { - field: 'host.id', - value: '8cc95778cce5407c809480e8e32ad76b', - }, - ], - cardinality: [ - { - field: 'destination.ip', - value: 7, - }, - ], - count: 788, - from: '2019-02-19T07:12:05.332Z', - }, - }); - }); - - it('should not generate signals if only one field meets the threshold requirement', async () => { - const rule: ThresholdCreateSchema = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: ['host.id', 'process.name'], - value: 22, - }, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(0); - }); - - it('generates signals from Threshold rules when bucketing by multiple fields', async () => { - const rule: ThresholdCreateSchema = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: ['host.id', 'process.name', 'event.module'], - value: 21, - }, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).eql(1); - const fullSignal = signalsOpen.hits.hits[0]._source; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - const eventIds = (fullSignal[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); - expect(fullSignal).eql({ - ...fullSignal, - 'event.module': 'system', - 'host.id': '2ab45fc1c41e4c84bbd02202a7e5761f', - 'process.name': 'sshd', - [EVENT_KIND]: 'signal', - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: eventIds[0], - index: 'auditbeat-*', - type: 'event', - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_REASON]: `event with process sshd, created high alert Signal Testing Query.`, - [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], - [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], - [ALERT_DEPTH]: 1, - [ALERT_THRESHOLD_RESULT]: { - terms: [ - { - field: 'host.id', - value: '2ab45fc1c41e4c84bbd02202a7e5761f', - }, - { - field: 'process.name', - value: 'sshd', - }, - { - field: 'event.module', - value: 'system', - }, - ], - count: 21, - from: '2019-02-19T20:22:03.561Z', - }, - }); - }); - - describe('Timestamp override and fallback', async () => { - before(async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' - ); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' - ); - }); - - it('applies timestamp override when using single field', async () => { - const rule: ThresholdCreateSchema = { - ...getThresholdRuleForSignalTesting(['timestamp-fallback-test']), - threshold: { - field: 'host.name', - value: 1, - }, - timestamp_override: 'event.ingested', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).eql(4); - - for (const hit of signalsOpen.hits.hits) { - const originalTime = hit._source?.[ALERT_ORIGINAL_TIME]; - const hostName = hit._source?.['host.name']; - if (hostName === 'host-1') { - expect(originalTime).eql('2020-12-16T15:15:18.570Z'); - } else if (hostName === 'host-2') { - expect(originalTime).eql('2020-12-16T15:16:18.570Z'); - } else if (hostName === 'host-3') { - expect(originalTime).eql('2020-12-16T16:15:18.570Z'); - } else { - expect(originalTime).eql('2020-12-16T16:16:18.570Z'); - } - } - }); - - it('applies timestamp override when using multiple fields', async () => { - const rule: ThresholdCreateSchema = { - ...getThresholdRuleForSignalTesting(['timestamp-fallback-test']), - threshold: { - field: ['host.name', 'source.ip'], - value: 1, - }, - timestamp_override: 'event.ingested', - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits.length).eql(4); - - for (const hit of signalsOpen.hits.hits) { - const originalTime = hit._source?.[ALERT_ORIGINAL_TIME]; - const hostName = hit._source?.['host.name']; - if (hostName === 'host-1') { - expect(originalTime).eql('2020-12-16T15:15:18.570Z'); - } else if (hostName === 'host-2') { - expect(originalTime).eql('2020-12-16T15:16:18.570Z'); - } else if (hostName === 'host-3') { - expect(originalTime).eql('2020-12-16T16:15:18.570Z'); - } else { - expect(originalTime).eql('2020-12-16T16:16:18.570Z'); - } - } - }); - }); - - describe('Threshold alerts should be be enriched', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - it('should be enriched with host risk score', async () => { - const rule: ThresholdCreateSchema = { - ...getThresholdRuleForSignalTesting(['auditbeat-*']), - threshold: { - field: 'host.name', - value: 100, - }, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 2, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_level).to.eql('Low'); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_score_norm).to.eql(20); - expect(signalsOpen.hits.hits[1]?._source?.host?.risk?.calculated_level).to.eql( - 'Critical' - ); - expect(signalsOpen.hits.hits[1]?._source?.host?.risk?.calculated_score_norm).to.eql(96); - }); - }); - }); - - describe('Enrich alerts: query rule', () => { - describe('without index avalable', () => { - it('should do not have risk score fields', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk).to.eql(undefined); - expect(signalsOpen.hits.hits[0]?._source?.user?.risk).to.eql(undefined); - }); - }); - - describe('with host risk score', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - }); - - it('should host have risk score field and do not have user risk score', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID} or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - - const alerts = signalsOpen.hits.hits ?? []; - const firstAlert = alerts.find( - (alert) => alert?._source?.host?.name === 'suricata-zeek-sensor-toronto' - ); - const secondAlert = alerts.find( - (alert) => alert?._source?.host?.name === 'suricata-sensor-london' - ); - const thirdAlert = alerts.find((alert) => alert?._source?.host?.name === 'IE11WIN8_1'); - - expect(firstAlert?._source?.host?.risk?.calculated_level).to.eql('Critical'); - expect(firstAlert?._source?.host?.risk?.calculated_score_norm).to.eql(96); - expect(firstAlert?._source?.user?.risk).to.eql(undefined); - expect(secondAlert?._source?.host?.risk?.calculated_level).to.eql('Low'); - expect(secondAlert?._source?.host?.risk?.calculated_score_norm).to.eql(20); - expect(thirdAlert?._source?.host?.risk).to.eql(undefined); - }); - }); - - describe('with host and risk score and user risk score', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); - await esArchiver.load('x-pack/test/functional/es_archives/entity/user_risk'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); - await esArchiver.unload('x-pack/test/functional/es_archives/entity/user_risk'); - }); - - it('should have host and user risk score fields', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsOpen = await getSignalsByIds(supertest, log, [id]); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_level).to.eql( - 'Critical' - ); - expect(signalsOpen.hits.hits[0]?._source?.host?.risk?.calculated_score_norm).to.eql(96); - expect(signalsOpen.hits.hits[0]?._source?.user?.risk?.calculated_level).to.eql('Low'); - expect(signalsOpen.hits.hits[0]?._source?.user?.risk?.calculated_score_norm).to.eql(11); - }); - }); - }); - }); - - /** - * Here we test the functionality of Severity and Risk Score overrides (also called "mappings" - * in the code). If the rule specifies a mapping, then the final Severity or Risk Score - * value of the signal will be taken from the mapped field of the source event. - */ - describe('Signals generated from events with custom severity and risk score fields', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/signals/severity_risk_overrides'); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/signals/severity_risk_overrides' - ); - }); - - const executeRuleAndGetSignals = async (rule: QueryCreateSchema) => { - const { id } = await createRule(supertest, log, rule); - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 4, [id]); - const signalsResponse = await getSignalsByIds(supertest, log, [id]); - const signals = signalsResponse.hits.hits.map((hit) => hit._source); - const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc'); - return signalsOrderedByEventId; - }; - - it('should get default severity and risk score if there is no mapping', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['signal_overrides']), - severity: 'medium', - risk_score: 75, - }; - - const signals = await executeRuleAndGetSignals(rule); - - expect(signals.length).equal(4); - signals.forEach((s) => { - expect(s?.[ALERT_SEVERITY]).equal('medium'); - expect(s?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([]); - - expect(s?.[ALERT_RISK_SCORE]).equal(75); - expect(s?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([]); - }); - }); - - it('should get overridden severity if the rule has a mapping for it', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['signal_overrides']), - severity: 'medium', - severity_mapping: [ - { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, - { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, - ], - risk_score: 75, - }; - - const signals = await executeRuleAndGetSignals(rule); - const severities = signals.map((s) => ({ - id: (s?.[ALERT_ANCESTORS] as Ancestor[])[0].id, - value: s?.[ALERT_SEVERITY], - })); - - expect(signals.length).equal(4); - expect(severities).eql([ - { id: '1', value: 'high' }, - { id: '2', value: 'critical' }, - { id: '3', value: 'critical' }, - { id: '4', value: 'critical' }, - ]); - - signals.forEach((s) => { - expect(s?.[ALERT_RISK_SCORE]).equal(75); - expect(s?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([]); - expect(s?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([ - { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, - { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, - ]); - }); - }); - - it('should get overridden risk score if the rule has a mapping for it', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['signal_overrides']), - severity: 'medium', - risk_score: 75, - risk_score_mapping: [ - { field: 'my_risk', operator: 'equals', value: '', risk_score: undefined }, - ], - }; - - const signals = await executeRuleAndGetSignals(rule); - const riskScores = signals.map((s) => ({ - id: (s?.[ALERT_ANCESTORS] as Ancestor[])[0].id, - value: s?.[ALERT_RISK_SCORE], - })); - - expect(signals.length).equal(4); - expect(riskScores).eql([ - { id: '1', value: 31.14 }, - { id: '2', value: 32.14 }, - { id: '3', value: 33.14 }, - { id: '4', value: 34.14 }, - ]); - - signals.forEach((s) => { - expect(s?.[ALERT_SEVERITY]).equal('medium'); - expect(s?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([]); - expect(s?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([ - { field: 'my_risk', operator: 'equals', value: '' }, - ]); - }); - }); - - it('should get overridden severity and risk score if the rule has both mappings', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['signal_overrides']), - severity: 'medium', - severity_mapping: [ - { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, - { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, - ], - risk_score: 75, - risk_score_mapping: [ - { field: 'my_risk', operator: 'equals', value: '', risk_score: undefined }, - ], - }; - - const signals = await executeRuleAndGetSignals(rule); - const values = signals.map((s) => ({ - id: (s?.[ALERT_ANCESTORS] as Ancestor[])[0].id, - severity: s?.[ALERT_SEVERITY], - risk: s?.[ALERT_RISK_SCORE], - })); - - expect(signals.length).equal(4); - expect(values).eql([ - { id: '1', severity: 'high', risk: 31.14 }, - { id: '2', severity: 'critical', risk: 32.14 }, - { id: '3', severity: 'critical', risk: 33.14 }, - { id: '4', severity: 'critical', risk: 34.14 }, - ]); - - signals.forEach((s) => { - expect(s?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([ - { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, - { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, - ]); - expect(s?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([ - { field: 'my_risk', operator: 'equals', value: '' }, - ]); - }); - }); - }); - - describe('Signals generated from events with name override field', async () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await deleteSignalsIndex(supertest, log); - await createSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - }); - - it('should generate signals with name_override field', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['auditbeat-*']), - rule_name_override: 'event.action', - }; - - const { id } = await createRule(supertest, log, rule); - - await waitForRuleSuccessOrStatus(supertest, log, id); - await waitForSignalsToBePresent(supertest, log, 1, [id]); - const signalsResponse = await getSignalsByIds(supertest, log, [id], 1); - const signals = signalsResponse.hits.hits.map((hit) => hit._source); - const signalsOrderedByEventId = orderBy(signals, 'signal.parent.id', 'asc'); - const fullSignal = signalsOrderedByEventId[0]; - if (!fullSignal) { - return expect(fullSignal).to.be.ok(); - } - - expect(fullSignal).eql({ - ...fullSignal, - [EVENT_ACTION]: 'boot', - [ALERT_ANCESTORS]: [ - { - depth: 0, - id: 'UBXOBmkBR346wHgnLP8T', - index: 'auditbeat-8.0.0-2019.02.19-000001', - type: 'event', - }, - ], - [ALERT_WORKFLOW_STATUS]: 'open', - [ALERT_REASON]: `event on zeek-sensor-amsterdam created high alert boot.`, - [ALERT_RULE_NAME]: 'boot', - [ALERT_RULE_RULE_NAME_OVERRIDE]: 'event.action', - [ALERT_DEPTH]: 1, - ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { - action: 'boot', - dataset: 'login', - kind: 'event', - module: 'system', - origin: '/var/log/wtmp', - }), - }); - }); - }); - - describe('Signal deduplication', async () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); - }); - - beforeEach(async () => { - await deleteSignalsIndex(supertest, log); - }); - - afterEach(async () => { - await deleteSignalsIndex(supertest, log); - await deleteAllAlerts(supertest, log); - }); - - it('should not generate duplicate signals', async () => { - const rule: QueryCreateSchema = { - ...getRuleForSignalTesting(['auditbeat-*']), - query: `_id:${ID}`, - }; - - const ruleResponse = await createRule(supertest, log, rule); - - const signals = await getOpenSignals(supertest, log, es, ruleResponse); - expect(signals.hits.hits.length).to.eql(1); - - const statusResponse = await supertest - .get(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .query({ id: ruleResponse.id }); - - // TODO: https://github.com/elastic/kibana/pull/121644 clean up, make type-safe - const ruleStatusDate = statusResponse.body?.execution_summary?.last_execution.date; - const initialStatusDate = new Date(ruleStatusDate); - - const initialSignal = signals.hits.hits[0]; - - // Disable the rule then re-enable to trigger another run - await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .send({ rule_id: ruleResponse.rule_id, enabled: false }) - .expect(200); - - await supertest - .patch(DETECTION_ENGINE_RULES_URL) - .set('kbn-xsrf', 'true') - .send({ rule_id: ruleResponse.rule_id, enabled: true }) - .expect(200); - - await waitForRuleSuccessOrStatus( - supertest, - log, - ruleResponse.id, - RuleExecutionStatus.succeeded, - initialStatusDate - ); - - const newSignals = await getOpenSignals(supertest, log, es, ruleResponse); - expect(newSignals.hits.hits.length).to.eql(1); - expect(newSignals.hits.hits[0]).to.eql(initialSignal); - }); - }); - }); -}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_prepackaged_rules_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_prepackaged_rules_status.ts index c3b2eb3568ccb..ce75086a2deea 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_prepackaged_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/get_prepackaged_rules_status.ts @@ -7,10 +7,12 @@ import expect from '@kbn/expect'; +import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { - DETECTION_ENGINE_PREPACKAGED_URL, - DETECTION_ENGINE_RULES_URL, -} from '@kbn/security-solution-plugin/common/constants'; + PREBUILT_RULES_STATUS_URL, + PREBUILT_RULES_URL, +} from '@kbn/security-solution-plugin/common/detection_engine/prebuilt_rules'; + import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -40,7 +42,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return expected JSON keys of the pre-packaged rules and pre-packaged timelines status', async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -58,7 +60,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return that rules_not_installed are greater than zero', async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -67,7 +69,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return that timelines_not_installed are greater than zero', async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -76,7 +78,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return that rules_custom_installed, rules_installed, and rules_not_updated are zero', async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -87,7 +89,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return that timelines_installed, and timelines_not_updated are zero', async () => { const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -103,7 +105,7 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200); const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); @@ -115,14 +117,10 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should show rules and timelines are installed when adding pre-packaged rules', async () => { - await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + await supertest.put(PREBUILT_RULES_URL).set('kbn-xsrf', 'true').send().expect(200); const { body } = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) + .get(PREBUILT_RULES_STATUS_URL) .set('kbn-xsrf', 'true') .send() .expect(200); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts index 3064d412da1bd..690498a287530 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/index.ts @@ -23,16 +23,13 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./create_rules')); loadTestFile(require.resolve('./preview_rules')); loadTestFile(require.resolve('./create_rules_bulk')); - loadTestFile(require.resolve('./create_ml')); loadTestFile(require.resolve('./create_new_terms')); - loadTestFile(require.resolve('./create_threat_matching')); loadTestFile(require.resolve('./create_rule_exceptions')); loadTestFile(require.resolve('./delete_rules')); loadTestFile(require.resolve('./delete_rules_bulk')); loadTestFile(require.resolve('./export_rules')); loadTestFile(require.resolve('./find_rules')); loadTestFile(require.resolve('./find_rule_exception_references')); - loadTestFile(require.resolve('./generating_signals')); loadTestFile(require.resolve('./get_prepackaged_rules_status')); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/preview_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/preview_rules.ts index 3e0dd166a0cd2..b38545e9c03c9 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/preview_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/preview_rules.ts @@ -20,8 +20,8 @@ export default ({ getService }: FtrProviderContext) => { const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); - describe('create_rules', () => { - describe('creating rules', () => { + describe('preview_rules', () => { + describe('previewing rules', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); }); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts index 8ee61c0705452..11ad72b505f1f 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { omit } from 'lodash'; -import { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createSignalsIndex, @@ -31,7 +31,7 @@ import { } from '../../utils'; // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: -// x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json +// x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint_security.json const RULE_ID = '9a1a2dae-0b5f-4c3d-8305-a268d404c306'; // eslint-disable-next-line import/no-default-export @@ -107,7 +107,7 @@ export default ({ getService }: FtrProviderContext) => { const hookAction = await createNewAction(supertest, log); const rule = getSimpleRule(); await createRule(supertest, log, rule); - const ruleToUpdate: CreateRulesSchema = { + const ruleToUpdate: RuleCreateProps = { ...getRuleWithWebHookAction(hookAction.id, true, rule), meta: {}, // create a rule with the action attached and a meta field }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts index e2ee8395d98c6..d3739473a5985 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/import_rules.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; -import { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; @@ -230,7 +230,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body } = await supertest .post(`${DETECTION_ENGINE_RULES_URL}/_import`) .set('kbn-xsrf', 'true') - .attach('file', ruleToNdjson(rule as CreateRulesSchema), 'rules.ndjson') + .attach('file', ruleToNdjson(rule as RuleCreateProps), 'rules.ndjson') .expect(200); expect(body.errors[0]).to.eql({ diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts index 77655b8ba150b..30b378d2a7eea 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts @@ -6,35 +6,33 @@ */ import expect from '@kbn/expect'; - import { DETECTION_ENGINE_RULES_BULK_ACTION, DETECTION_ENGINE_RULES_URL, NOTIFICATION_THROTTLE_NO_ACTIONS, NOTIFICATION_THROTTLE_RULE, } from '@kbn/security-solution-plugin/common/constants'; - +import type { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { BulkAction, BulkActionEditType, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request/perform_bulk_action_schema'; -import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +} from '@kbn/security-solution-plugin/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { binaryToString, + createLegacyRuleAction, createRule, createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, - getSimpleRule, - getSimpleRuleOutput, - removeServerGeneratedProperties, - createLegacyRuleAction, getLegacyActionSO, - installPrePackagedRules, getSimpleMlRule, - getWebHookAction, + getSimpleRule, + getSimpleRuleOutput, getSlackAction, + getWebHookAction, + installPrePackagedRules, + removeServerGeneratedProperties, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -374,7 +372,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(rulesResponse.total).to.eql(2); - rulesResponse.data.forEach((rule: FullResponseSchema) => { + rulesResponse.data.forEach((rule: RuleResponse) => { expect(rule.actions).to.eql([ { action_type_id: '.slack', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts index e9c3b3b68487d..b893f38f20310 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action_dry_run.ts @@ -4,25 +4,24 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import expect from 'expect'; - import { DETECTION_ENGINE_RULES_BULK_ACTION, DETECTION_ENGINE_RULES_URL, } from '@kbn/security-solution-plugin/common/constants'; +import expect from 'expect'; import { BulkAction, BulkActionEditType, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request/perform_bulk_action_schema'; +} from '@kbn/security-solution-plugin/common/detection_engine/rule_management/api/rules/bulk_actions/request_schema'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, + getSimpleMlRule, getSimpleRule, installPrePackagedRules, - getSimpleMlRule, } from '../../utils'; // eslint-disable-next-line import/no-default-export diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/throttle.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/throttle.ts index 9a459b1ffb0e1..fc3dc32483ba4 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/throttle.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/throttle.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; -import { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { DETECTION_ENGINE_RULES_URL, NOTIFICATION_THROTTLE_NO_ACTIONS, @@ -73,7 +73,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('When creating throttle with "NOTIFICATION_THROTTLE_NO_ACTIONS" set and no actions, the rule should have its kibana alerting "mute_all" set to "true" and notify_when set to "onActiveAlert"', async () => { - const ruleWithThrottle: CreateRulesSchema = { + const ruleWithThrottle: RuleCreateProps = { ...getSimpleRule(), throttle: NOTIFICATION_THROTTLE_NO_ACTIONS, }; @@ -93,7 +93,7 @@ export default ({ getService }: FtrProviderContext) => { .send(getWebHookAction()) .expect(200); - const ruleWithThrottle: CreateRulesSchema = { + const ruleWithThrottle: RuleCreateProps = { ...getRuleWithWebHookAction(hookAction.id), throttle: NOTIFICATION_THROTTLE_NO_ACTIONS, }; @@ -106,7 +106,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('When creating throttle with "NOTIFICATION_THROTTLE_RULE" set and no actions, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to "onActiveAlert"', async () => { - const ruleWithThrottle: CreateRulesSchema = { + const ruleWithThrottle: RuleCreateProps = { ...getSimpleRule(), throttle: NOTIFICATION_THROTTLE_RULE, }; @@ -120,7 +120,7 @@ export default ({ getService }: FtrProviderContext) => { // NOTE: This shows A side effect of how we do not set data on side cars anymore where the user is told they have no actions since the array is empty. it('When creating throttle with "NOTIFICATION_THROTTLE_RULE" set and no actions, since we do not have any actions, we should get back a throttle of "NOTIFICATION_THROTTLE_NO_ACTIONS"', async () => { - const ruleWithThrottle: CreateRulesSchema = { + const ruleWithThrottle: RuleCreateProps = { ...getSimpleRule(), throttle: NOTIFICATION_THROTTLE_RULE, }; @@ -136,7 +136,7 @@ export default ({ getService }: FtrProviderContext) => { .send(getWebHookAction()) .expect(200); - const ruleWithThrottle: CreateRulesSchema = { + const ruleWithThrottle: RuleCreateProps = { ...getRuleWithWebHookAction(hookAction.id), throttle: NOTIFICATION_THROTTLE_RULE, }; @@ -149,7 +149,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('When creating throttle with "1h" set and no actions, the rule should have its kibana alerting "mute_all" set to "false" and notify_when set to "onThrottleInterval"', async () => { - const ruleWithThrottle: CreateRulesSchema = { + const ruleWithThrottle: RuleCreateProps = { ...getSimpleRule(), throttle: '1h', }; @@ -169,7 +169,7 @@ export default ({ getService }: FtrProviderContext) => { .send(getWebHookAction()) .expect(200); - const ruleWithThrottle: CreateRulesSchema = { + const ruleWithThrottle: RuleCreateProps = { ...getRuleWithWebHookAction(hookAction.id), throttle: '1h', }; @@ -197,7 +197,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('When creating throttle with "NOTIFICATION_THROTTLE_NO_ACTIONS" set and no actions, we should return "NOTIFICATION_THROTTLE_NO_ACTIONS" when doing a read', async () => { - const ruleWithThrottle: CreateRulesSchema = { + const ruleWithThrottle: RuleCreateProps = { ...getSimpleRule(), throttle: NOTIFICATION_THROTTLE_NO_ACTIONS, }; @@ -208,7 +208,7 @@ export default ({ getService }: FtrProviderContext) => { // NOTE: This shows A side effect of how we do not set data on side cars anymore where the user is told they have no actions since the array is empty. it('When creating throttle with "NOTIFICATION_THROTTLE_RULE" set and no actions, since we do not have any actions, we should get back a throttle of "NOTIFICATION_THROTTLE_NO_ACTIONS" when doing a read', async () => { - const ruleWithThrottle: CreateRulesSchema = { + const ruleWithThrottle: RuleCreateProps = { ...getSimpleRule(), throttle: NOTIFICATION_THROTTLE_RULE, }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/timestamps.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/timestamps.ts index db7ed86b97f71..df0d9fb17e47a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/timestamps.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/timestamps.ts @@ -9,9 +9,9 @@ import expect from '@kbn/expect'; import { orderBy } from 'lodash'; import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; import { - EqlCreateSchema, - QueryCreateSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + EqlRuleCreateProps, + QueryRuleCreateProps, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -77,7 +77,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should still use the @timestamp field even with an override field. It should never use the override field', async () => { - const rule: QueryCreateSchema = { + const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['myfakeindex-5']), timestamp_override: 'event.ingested', }; @@ -106,7 +106,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should still use the @timestamp field even with an override field. It should never use the override field', async () => { - const rule: EqlCreateSchema = { + const rule: EqlRuleCreateProps = { ...getEqlRuleForSignalTesting(['myfakeindex-5']), timestamp_override: 'event.ingested', }; @@ -165,7 +165,7 @@ export default ({ getService }: FtrProviderContext) => { describe('KQL', () => { it('should generate signals with event.ingested, @timestamp and (event.ingested + timestamp)', async () => { - const rule: QueryCreateSchema = { + const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['myfa*']), timestamp_override: 'event.ingested', }; @@ -187,7 +187,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate 2 signals with event.ingested when timestamp fallback is disabled', async () => { - const rule: QueryCreateSchema = { + const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['myfa*']), rule_id: 'rule-without-timestamp-fallback', timestamp_override: 'event.ingested', @@ -211,7 +211,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate 2 signals with @timestamp', async () => { - const rule: QueryCreateSchema = getRuleForSignalTesting(['myfa*']); + const rule: QueryRuleCreateProps = getRuleForSignalTesting(['myfa*']); const { id } = await createRule(supertest, log, rule); @@ -230,7 +230,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate 2 signals when timestamp override does not exist', async () => { - const rule: QueryCreateSchema = { + const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['myfa*']), timestamp_override: 'event.fakeingestfield', }; @@ -251,7 +251,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not generate any signals when timestamp override does not exist and timestamp fallback is disabled', async () => { - const rule: QueryCreateSchema = { + const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['myfa*']), rule_id: 'rule-without-timestamp-fallback', timestamp_override: 'event.fakeingestfield', @@ -276,7 +276,7 @@ export default ({ getService }: FtrProviderContext) => { * and we add a new timestamp to the signal. */ it('should NOT use the timestamp override as the "original_time"', async () => { - const rule: QueryCreateSchema = { + const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['myfakeindex-2']), timestamp_override: 'event.ingested', }; @@ -294,7 +294,7 @@ export default ({ getService }: FtrProviderContext) => { describe('EQL', () => { it('should generate 2 signals with @timestamp', async () => { - const rule: EqlCreateSchema = getEqlRuleForSignalTesting(['myfa*']); + const rule: EqlRuleCreateProps = getEqlRuleForSignalTesting(['myfa*']); const { id } = await createRule(supertest, log, rule); @@ -313,7 +313,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate 2 signals when timestamp override does not exist', async () => { - const rule: EqlCreateSchema = { + const rule: EqlRuleCreateProps = { ...getEqlRuleForSignalTesting(['myfa*']), timestamp_override: 'event.fakeingestfield', }; @@ -334,7 +334,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not generate any signals when timestamp override does not exist and timestamp fallback is disabled', async () => { - const rule: EqlCreateSchema = { + const rule: EqlRuleCreateProps = { ...getEqlRuleForSignalTesting(['myfa*']), timestamp_override: 'event.fakeingestfield', timestamp_override_fallback_disabled: true, @@ -383,7 +383,7 @@ export default ({ getService }: FtrProviderContext) => { * ref: https://github.com/elastic/elasticsearch/issues/28806#issuecomment-369303620 */ it('should generate 200 signals when timestamp override does not exist', async () => { - const rule: QueryCreateSchema = { + const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['auditbeat-*']), timestamp_override: 'event.fakeingested', max_signals: 200, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts index 2f5fb63382552..a3f6812f6a0b0 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules_bulk.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { DETECTION_ENGINE_RULES_URL, @@ -158,7 +158,7 @@ export default ({ getService }: FtrProviderContext) => { updatedRule2.throttle = '1d'; // update both rule names - const { body }: { body: FullResponseSchema[] } = await supertest + const { body }: { body: RuleResponse[] } = await supertest .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1, updatedRule2]) @@ -220,7 +220,7 @@ export default ({ getService }: FtrProviderContext) => { updatedRule2.name = 'some other name'; // update both rule names - const { body }: { body: FullResponseSchema[] } = await supertest + const { body }: { body: RuleResponse[] } = await supertest .put(DETECTION_ENGINE_RULES_BULK_UPDATE) .set('kbn-xsrf', 'true') .send([updatedRule1, updatedRule2]) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts index cc97253bfaf67..f6ea0fc02747b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group3/create_exceptions.ts @@ -11,12 +11,12 @@ import expect from '@kbn/expect'; import type { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; import type { - CreateRulesSchema, - EqlCreateSchema, - QueryCreateSchema, - ThreatMatchCreateSchema, - ThresholdCreateSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + RuleCreateProps, + EqlRuleCreateProps, + QueryRuleCreateProps, + ThreatMatchRuleCreateProps, + ThresholdRuleCreateProps, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock'; @@ -92,7 +92,7 @@ export default ({ getService }: FtrProviderContext) => { .send(getCreateExceptionListMinimalSchemaMock()) .expect(200); - const ruleWithException: CreateRulesSchema = { + const ruleWithException: RuleCreateProps = { ...getSimpleRule(), exceptions_list: [ { @@ -129,7 +129,7 @@ export default ({ getService }: FtrProviderContext) => { .send(getCreateExceptionListMinimalSchemaMock()) .expect(200); - const ruleWithException: CreateRulesSchema = { + const ruleWithException: RuleCreateProps = { ...getSimpleRule(), enabled: true, exceptions_list: [ @@ -165,7 +165,7 @@ export default ({ getService }: FtrProviderContext) => { await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to use const immutableRule = await getRule( supertest, @@ -199,7 +199,7 @@ export default ({ getService }: FtrProviderContext) => { ); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to use const immutableRule = await getRule( supertest, @@ -239,7 +239,7 @@ export default ({ getService }: FtrProviderContext) => { await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to use const immutableRule = await getRule( supertest, @@ -277,7 +277,7 @@ export default ({ getService }: FtrProviderContext) => { ); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule const immutableRule = await getRule( supertest, @@ -326,7 +326,7 @@ export default ({ getService }: FtrProviderContext) => { await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule const immutableRule = await getRule( supertest, @@ -361,7 +361,7 @@ export default ({ getService }: FtrProviderContext) => { ); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule const immutableRule = await getRule( supertest, @@ -418,7 +418,7 @@ export default ({ getService }: FtrProviderContext) => { ); // Rule id of "eb079c62-4481-4d6e-9643-3ca499df7aaa" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/external_alerts.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/external_alerts.json // since this rule does not have existing exceptions_list that we are going to use for tests const immutableRule = await getRule( supertest, @@ -472,7 +472,7 @@ export default ({ getService }: FtrProviderContext) => { ); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to use const immutableRule = await getRule( supertest, @@ -522,7 +522,7 @@ export default ({ getService }: FtrProviderContext) => { ); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint.json // This rule has an existing exceptions_list that we are going to use const immutableRule = await getRule( supertest, @@ -623,7 +623,7 @@ export default ({ getService }: FtrProviderContext) => { }; await createExceptionListItem(supertest, log, exceptionListItem); - const ruleWithException: CreateRulesSchema = { + const ruleWithException: RuleCreateProps = { name: 'Simple Rule Query', description: 'Simple Rule Query', enabled: true, @@ -651,7 +651,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to execute against an exception list that does include valid entries and get back 0 signals', async () => { - const rule: QueryCreateSchema = { + const rule: QueryRuleCreateProps = { name: 'Simple Rule Query', description: 'Simple Rule Query', enabled: true, @@ -678,7 +678,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('generates no signals when an exception is added for an EQL rule', async () => { - const rule: EqlCreateSchema = { + const rule: EqlRuleCreateProps = { ...getEqlRuleForSignalTesting(['auditbeat-*']), query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', }; @@ -697,7 +697,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('generates no signals when an exception is added for a threshold rule', async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getThresholdRuleForSignalTesting(['auditbeat-*']), threshold: { field: 'host.id', @@ -719,7 +719,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('generates no signals when an exception is added for a threat match rule', async () => { - const rule: ThreatMatchCreateSchema = { + const rule: ThreatMatchRuleCreateProps = { description: 'Detecting root and admin users', name: 'Query with a rule id', severity: 'high', @@ -772,7 +772,7 @@ export default ({ getService }: FtrProviderContext) => { it('generates no signals when a value list exception is added for a query rule', async () => { const valueListId = 'value-list-id'; await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId); - const rule: QueryCreateSchema = { + const rule: QueryRuleCreateProps = { name: 'Simple Rule Query', description: 'Simple Rule Query', enabled: true, @@ -804,7 +804,7 @@ export default ({ getService }: FtrProviderContext) => { it('generates no signals when a value list exception is added for a threat match rule', async () => { const valueListId = 'value-list-id'; await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId); - const rule: ThreatMatchCreateSchema = { + const rule: ThreatMatchRuleCreateProps = { description: 'Detecting root and admin users', name: 'Query with a rule id', severity: 'high', @@ -852,7 +852,7 @@ export default ({ getService }: FtrProviderContext) => { it('generates no signals when a value list exception is added for a threshold rule', async () => { const valueListId = 'value-list-id'; await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId); - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { description: 'Detecting root and admin users', name: 'Query with a rule id', severity: 'high', @@ -889,7 +889,7 @@ export default ({ getService }: FtrProviderContext) => { it('generates no signals when a value list exception is added for an EQL rule', async () => { const valueListId = 'value-list-id'; await importFile(supertest, log, 'keyword', ['zeek-sensor-amsterdam'], valueListId); - const rule: EqlCreateSchema = { + const rule: EqlRuleCreateProps = { ...getEqlRuleForSignalTesting(['auditbeat-*']), query: 'configuration where host.name=="zeek-sensor-amsterdam"', }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts index eb5f5c9a923bb..36f5b84a50c3d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/task_based/detection_rules.ts @@ -33,7 +33,7 @@ export default ({ getService }: FtrProviderContext) => { const retry = getService('retry'); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint_security.json // This rule has an existing exceptions_list that we are going to use. const IMMUTABLE_RULE_ID = '9a1a2dae-0b5f-4c3d-8305-a268d404c306'; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/usage_collector/detection_rule_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/usage_collector/detection_rule_status.ts index dea6703d2fef4..0581c97802f03 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/usage_collector/detection_rule_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/usage_collector/detection_rule_status.ts @@ -10,9 +10,9 @@ import type { MlJobUsageMetric } from '@kbn/security-solution-plugin/server/usag import type { RulesTypeUsage } from '@kbn/security-solution-plugin/server/usage/detections/rules/types'; import type { DetectionMetrics } from '@kbn/security-solution-plugin/server/usage/detections/types'; import type { - ThreatMatchCreateSchema, - ThresholdCreateSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + ThreatMatchRuleCreateProps, + ThresholdRuleCreateProps, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { getInitialMlJobUsage } from '@kbn/security-solution-plugin/server/usage/detections/ml_jobs/get_initial_usage'; import { getInitialDetectionMetrics } from '@kbn/security-solution-plugin/server/usage/detections/get_initial_usage'; import { @@ -444,7 +444,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"threshold" rule type', () => { let stats: DetectionMetrics | undefined; before(async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getThresholdRuleForSignalTesting(['telemetry']), threshold: { field: 'keyword', @@ -644,7 +644,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"indicator_match/threat_match" rule type', () => { let stats: DetectionMetrics | undefined; before(async () => { - const rule: ThreatMatchCreateSchema = { + const rule: ThreatMatchRuleCreateProps = { ...getSimpleThreatMatch('rule-1', true), index: ['telemetry'], threat_index: ['telemetry'], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/usage_collector/detection_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/usage_collector/detection_rules.ts index 7f7b0d3d30788..1d059e02ed5cc 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/usage_collector/detection_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group4/telemetry/usage_collector/detection_rules.ts @@ -8,9 +8,9 @@ import expect from '@kbn/expect'; import type { DetectionMetrics } from '@kbn/security-solution-plugin/server/usage/detections/types'; import type { - ThreatMatchCreateSchema, - ThresholdCreateSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + ThreatMatchRuleCreateProps, + ThresholdRuleCreateProps, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { getInitialDetectionMetrics } from '@kbn/security-solution-plugin/server/usage/detections/get_initial_usage'; import { getInitialEventLogUsage } from '@kbn/security-solution-plugin/server/usage/detections/rules/get_initial_usage'; import type { FtrProviderContext } from '../../../../common/ftr_provider_context'; @@ -512,7 +512,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"threshold" rule type', () => { it('should show "notifications_enabled", "notifications_disabled" "legacy_notifications_enabled", "legacy_notifications_disabled", all to be "0" for "disabled"/"in-active" rule that does not have any actions', async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getThresholdRuleForSignalTesting(['telemetry'], 'rule-1', false), threshold: { field: 'keyword', @@ -552,7 +552,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should show "notifications_enabled", "notifications_disabled" "legacy_notifications_enabled", "legacy_notifications_disabled", all to be "0" for "enabled"/"active" rule that does not have any actions', async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getThresholdRuleForSignalTesting(['telemetry']), threshold: { field: 'keyword', @@ -600,7 +600,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should show "notifications_disabled" to be "1" for rule that has at least "1" action(s) and the alert is "disabled"/"in-active"', async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getThresholdRuleForSignalTesting(['telemetry'], 'rule-1', false), threshold: { field: 'keyword', @@ -641,7 +641,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should show "notifications_enabled" to be "1" for rule that has at least "1" action(s) and the alert is "enabled"/"active"', async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getThresholdRuleForSignalTesting(['telemetry']), threshold: { field: 'keyword', @@ -686,7 +686,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should show "legacy_notifications_disabled" to be "1" for rule that has at least "1" legacy action(s) and the alert is "disabled"/"in-active"', async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getThresholdRuleForSignalTesting(['telemetry'], 'rule-1', false), threshold: { field: 'keyword', @@ -727,7 +727,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should show "legacy_notifications_enabled" to be "1" for rule that has at least "1" legacy action(s) and the alert is "enabled"/"active"', async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getThresholdRuleForSignalTesting(['telemetry']), threshold: { field: 'keyword', @@ -1029,7 +1029,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should show "notifications_enabled", "notifications_disabled" "legacy_notifications_enabled", "legacy_notifications_disabled", all to be "0" for "enabled"/"active" rule that does not have any actions', async () => { - const rule: ThreatMatchCreateSchema = { + const rule: ThreatMatchRuleCreateProps = { ...getSimpleThreatMatch('rule-1', true), index: ['telemetry'], threat_index: ['telemetry'], @@ -1121,7 +1121,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should show "notifications_enabled" to be "1" for rule that has at least "1" action(s) and the alert is "enabled"/"active"', async () => { - const rule: ThreatMatchCreateSchema = { + const rule: ThreatMatchRuleCreateProps = { ...getSimpleThreatMatch('rule-1', true), index: ['telemetry'], threat_index: ['telemetry'], @@ -1210,7 +1210,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should show "legacy_notifications_enabled" to be "1" for rule that has at least "1" legacy action(s) and the alert is "enabled"/"active"', async () => { - const rule: ThreatMatchCreateSchema = { + const rule: ThreatMatchRuleCreateProps = { ...getSimpleThreatMatch('rule-1', true), index: ['telemetry'], threat_index: ['telemetry'], @@ -1303,7 +1303,7 @@ export default ({ getService }: FtrProviderContext) => { await retry.try(async () => { const stats = await getStats(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint_security.json // We have to search by "rule_name" since the "rule_id" it is storing is the Saved Object ID and not the rule_id const foundRule = stats.detection_rules.detection_rule_detail.find( (rule) => rule.rule_id === '9a1a2dae-0b5f-4c3d-8305-a268d404c306' @@ -1334,7 +1334,7 @@ export default ({ getService }: FtrProviderContext) => { it('should show "notifications_disabled" to be "1", "has_notification" to be "true, "has_legacy_notification" to be "false" for rule that has at least "1" action(s) and the alert is "disabled"/"in-active"', async () => { await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint_security.json const immutableRule = await getRule(supertest, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); const hookAction = await createNewAction(supertest, log); const newRuleToUpdate = getSimpleRule(immutableRule.rule_id); @@ -1388,7 +1388,7 @@ export default ({ getService }: FtrProviderContext) => { it('should show "notifications_enabled" to be "1", "has_notification" to be "true, "has_legacy_notification" to be "false" for rule that has at least "1" action(s) and the alert is "enabled"/"active"', async () => { await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint_security.json const immutableRule = await getRule(supertest, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); const hookAction = await createNewAction(supertest, log); const newRuleToUpdate = getSimpleRule(immutableRule.rule_id); @@ -1442,7 +1442,7 @@ export default ({ getService }: FtrProviderContext) => { it('should show "legacy_notifications_disabled" to be "1", "has_notification" to be "false, "has_legacy_notification" to be "true" for rule that has at least "1" action(s) and the alert is "disabled"/"in-active"', async () => { await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint_security.json const immutableRule = await getRule(supertest, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); const hookAction = await createNewAction(supertest, log); const newRuleToUpdate = getSimpleRule(immutableRule.rule_id, false); @@ -1496,7 +1496,7 @@ export default ({ getService }: FtrProviderContext) => { it('should show "legacy_notifications_enabled" to be "1", "has_notification" to be "false, "has_legacy_notification" to be "true" for rule that has at least "1" action(s) and the alert is "enabled"/"active"', async () => { await installPrePackagedRules(supertest, log); // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json + // x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint_security.json const immutableRule = await getRule(supertest, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); const hookAction = await createNewAction(supertest, log); const newRuleToUpdate = getSimpleRule(immutableRule.rule_id, true); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/const_keyword.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/const_keyword.ts index 011ed04376281..f0290b8258dd8 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/const_keyword.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/const_keyword.ts @@ -7,9 +7,9 @@ import expect from '@kbn/expect'; import { - EqlCreateSchema, - ThresholdCreateSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + EqlRuleCreateProps, + ThresholdRuleCreateProps, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { ALERT_THRESHOLD_RESULT } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -86,7 +86,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"eql" rule type', () => { it('should detect the "dataset_name_1" from "event.dataset" and have 4 signals', async () => { - const rule: EqlCreateSchema = { + const rule: EqlRuleCreateProps = { ...getEqlRuleForSignalTesting(['const_keyword']), query: 'any where event.dataset=="dataset_name_1"', }; @@ -99,7 +99,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should copy the "dataset_name_1" from "event.dataset"', async () => { - const rule: EqlCreateSchema = { + const rule: EqlRuleCreateProps = { ...getEqlRuleForSignalTesting(['const_keyword']), query: 'any where event.dataset=="dataset_name_1"', }; @@ -120,7 +120,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"threshold" rule type', async () => { it('should detect the "dataset_name_1" from "event.dataset"', async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getThresholdRuleForSignalTesting(['const_keyword']), threshold: { field: 'event.dataset', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/keyword.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/keyword.ts index c07f0efb1df98..ef8126015c758 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/keyword.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/keyword.ts @@ -8,10 +8,10 @@ import expect from '@kbn/expect'; import { - EqlCreateSchema, - QueryCreateSchema, - ThresholdCreateSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + EqlRuleCreateProps, + QueryRuleCreateProps, + ThresholdRuleCreateProps, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { ALERT_THRESHOLD_RESULT } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { @@ -53,7 +53,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"kql" rule type', () => { it('should detect the "dataset_name_1" from "event.dataset"', async () => { - const rule: QueryCreateSchema = { + const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['keyword']), query: 'event.dataset: "dataset_name_1"', }; @@ -73,7 +73,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"eql" rule type', () => { it('should detect the "dataset_name_1" from "event.dataset"', async () => { - const rule: EqlCreateSchema = { + const rule: EqlRuleCreateProps = { ...getEqlRuleForSignalTesting(['keyword']), query: 'any where event.dataset=="dataset_name_1"', }; @@ -94,7 +94,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"threshold" rule type', async () => { it('should detect the "dataset_name_1" from "event.dataset"', async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getThresholdRuleForSignalTesting(['keyword']), threshold: { field: 'event.dataset', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/keyword_mixed_with_const.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/keyword_mixed_with_const.ts index 52c5ae615da95..5949770ef23f9 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/keyword_mixed_with_const.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group5/keyword_family/keyword_mixed_with_const.ts @@ -7,9 +7,9 @@ import expect from '@kbn/expect'; import { - EqlCreateSchema, - ThresholdCreateSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + EqlRuleCreateProps, + ThresholdRuleCreateProps, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { ALERT_THRESHOLD_RESULT } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -91,7 +91,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"eql" rule type', () => { it('should detect the "dataset_name_1" from "event.dataset" and have 8 signals, 4 from each index', async () => { - const rule: EqlCreateSchema = { + const rule: EqlRuleCreateProps = { ...getEqlRuleForSignalTesting(['keyword', 'const_keyword']), query: 'any where event.dataset=="dataset_name_1"', }; @@ -104,7 +104,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should copy the "dataset_name_1" from "event.dataset"', async () => { - const rule: EqlCreateSchema = { + const rule: EqlRuleCreateProps = { ...getEqlRuleForSignalTesting(['keyword', 'const_keyword']), query: 'any where event.dataset=="dataset_name_1"', }; @@ -129,7 +129,7 @@ export default ({ getService }: FtrProviderContext) => { describe('"threshold" rule type', async () => { it('should detect the "dataset_name_1" from "event.dataset"', async () => { - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...getRuleForSignalTesting(['keyword', 'const_keyword']), rule_id: 'threshold-rule', type: 'threshold', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts index ba8c9ea88cd17..343de21a3ebf5 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts @@ -14,12 +14,12 @@ import { } from '@kbn/security-solution-plugin/common/constants'; import { ThreatEcs } from '@kbn/security-solution-plugin/common/ecs/threat'; import { - EqlCreateSchema, - QueryCreateSchema, - SavedQueryCreateSchema, - ThreatMatchCreateSchema, - ThresholdCreateSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + EqlRuleCreateProps, + QueryRuleCreateProps, + SavedQueryRuleCreateProps, + ThreatMatchRuleCreateProps, + ThresholdRuleCreateProps, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { createRule, createSignalsIndex, @@ -181,7 +181,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate a signal-on-legacy-signal with legacy index pattern', async () => { - const rule: ThreatMatchCreateSchema = getThreatMatchRuleForSignalTesting([ + const rule: ThreatMatchRuleCreateProps = getThreatMatchRuleForSignalTesting([ '.siem-signals-*', ]); const { id } = await createRule(supertest, log, rule); @@ -194,7 +194,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate a signal-on-legacy-signal with AAD index pattern', async () => { - const rule: ThreatMatchCreateSchema = getThreatMatchRuleForSignalTesting([ + const rule: ThreatMatchRuleCreateProps = getThreatMatchRuleForSignalTesting([ `.alerts-security.alerts-default`, ]); const { id } = await createRule(supertest, log, rule); @@ -222,7 +222,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate a signal-on-legacy-signal with legacy index pattern', async () => { - const rule: QueryCreateSchema = getRuleForSignalTesting([`.siem-signals-*`]); + const rule: QueryRuleCreateProps = getRuleForSignalTesting([`.siem-signals-*`]); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccessOrStatus(supertest, log, id); await waitForSignalsToBePresent(supertest, log, 1, [id]); @@ -389,7 +389,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate a signal-on-legacy-signal with AAD index pattern', async () => { - const rule: QueryCreateSchema = getRuleForSignalTesting([ + const rule: QueryRuleCreateProps = getRuleForSignalTesting([ `.alerts-security.alerts-default`, ]); const { id } = await createRule(supertest, log, rule); @@ -573,7 +573,9 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate a signal-on-legacy-signal with legacy index pattern', async () => { - const rule: SavedQueryCreateSchema = getSavedQueryRuleForSignalTesting([`.siem-signals-*`]); + const rule: SavedQueryRuleCreateProps = getSavedQueryRuleForSignalTesting([ + `.siem-signals-*`, + ]); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccessOrStatus(supertest, log, id); await waitForSignalsToBePresent(supertest, log, 1, [id]); @@ -584,7 +586,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate a signal-on-legacy-signal with AAD index pattern', async () => { - const rule: SavedQueryCreateSchema = getSavedQueryRuleForSignalTesting([ + const rule: SavedQueryRuleCreateProps = getSavedQueryRuleForSignalTesting([ `.alerts-security.alerts-default`, ]); const { id } = await createRule(supertest, log, rule); @@ -612,7 +614,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate a signal-on-legacy-signal with legacy index pattern', async () => { - const rule: EqlCreateSchema = getEqlRuleForSignalTesting(['.siem-signals-*']); + const rule: EqlRuleCreateProps = getEqlRuleForSignalTesting(['.siem-signals-*']); const { id } = await createRule(supertest, log, rule); await waitForRuleSuccessOrStatus(supertest, log, id); await waitForSignalsToBePresent(supertest, log, 1, [id]); @@ -623,7 +625,7 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate a signal-on-legacy-signal with AAD index pattern', async () => { - const rule: EqlCreateSchema = getEqlRuleForSignalTesting([ + const rule: EqlRuleCreateProps = getEqlRuleForSignalTesting([ `.alerts-security.alerts-default`, ]); const { id } = await createRule(supertest, log, rule); @@ -651,10 +653,10 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate a signal-on-legacy-signal with legacy index pattern', async () => { - const baseRule: ThresholdCreateSchema = getThresholdRuleForSignalTesting([ + const baseRule: ThresholdRuleCreateProps = getThresholdRuleForSignalTesting([ '.siem-signals-*', ]); - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...baseRule, threshold: { ...baseRule.threshold, @@ -672,10 +674,10 @@ export default ({ getService }: FtrProviderContext) => { }); it('should generate a signal-on-legacy-signal with AAD index pattern', async () => { - const baseRule: ThresholdCreateSchema = getThresholdRuleForSignalTesting([ + const baseRule: ThresholdRuleCreateProps = getThresholdRuleForSignalTesting([ `.alerts-security.alerts-default`, ]); - const rule: ThresholdCreateSchema = { + const rule: ThresholdRuleCreateProps = { ...baseRule, threshold: { ...baseRule.threshold, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/README.md b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/README.md new file mode 100644 index 0000000000000..3a72c90e3ec54 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/README.md @@ -0,0 +1,11 @@ +### Security Rule Execution Logic Tests + +These tests use the rule preview API as a fast way to verify that rules generate alerts as expected with various parameter settings. This avoids the costly overhead of creating a real rule and waiting for it to be scheduled. The preview route also returns rule statuses directly in the API response instead of writing the statuses to saved objects, which saves significant time as well. + +For assurance that the real rule execution works, one test for each rule type still creates a real rule and waits for the execution through the alerting framework and resulting alerts. + +As a result, the tests here typically run ~10x faster than the tests they replaced that were creating actual rules and running them. We can therefore add more tests here and get better coverage of the rule execution logic (which is currently, as of 8.5, somewhat lacking). + +Since the rule execution logic is primarily focused around generating and executing Elasticsearch queries, we need significant testing around whether or not the queries are returning the expected results. This is not achievable with unit tests at the moment, since we need to mock Elasticsearch results. The tests here are the preferred way to ensure that rules are executing the correct logic to generate alerts from source data. + +Testing rules with exceptions is still slow, even with the preview API, because the exception list has to be created for real and then cleaned up after the test - exceptions live in saved objects, so creating exceptions for individual tests slows them down significantly (>1s per test vs ~200ms for a test without exceptions). This is an area for future improvement. diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.ts new file mode 100644 index 0000000000000..2430b8f2148d9 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/config.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. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +// eslint-disable-next-line import/no-default-export +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../config.base.ts')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + }; +} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/eql.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/eql.ts new file mode 100644 index 0000000000000..cffc0a311ef3f --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/eql.ts @@ -0,0 +1,606 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { + ALERT_REASON, + ALERT_RULE_UUID, + ALERT_WORKFLOW_STATUS, + EVENT_KIND, +} from '@kbn/rule-data-utils'; +import { flattenWithPrefix } from '@kbn/securitysolution-rules'; + +import { get } from 'lodash'; + +import { EqlRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; +import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; +import { + ALERT_ANCESTORS, + ALERT_DEPTH, + ALERT_ORIGINAL_TIME, + ALERT_ORIGINAL_EVENT, + ALERT_ORIGINAL_EVENT_CATEGORY, + ALERT_GROUP_ID, +} from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { + createRule, + deleteAllAlerts, + deleteSignalsIndex, + getEqlRuleForSignalTesting, + getOpenSignals, + getPreviewAlerts, + previewRule, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('EQL type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/timestamp_override_6' + ); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/timestamp_override_6' + ); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + // First test creates a real rule - remaining tests use preview API + it('generates a correctly formatted signal from EQL non-sequence queries', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', + }; + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + expect(alerts.hits.hits.length).eql(1); + const fullSignal = alerts.hits.hits[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + + expect(fullSignal).eql({ + ...fullSignal, + agent: { + ephemeral_id: '0010d67a-14f7-41da-be30-489fea735967', + hostname: 'suricata-zeek-sensor-toronto', + id: 'a1d7b39c-f898-4dbe-a761-efb61939302d', + type: 'auditbeat', + version: '8.0.0', + }, + auditd: { + data: { + audit_enabled: '1', + old: '1', + }, + message_type: 'config_change', + result: 'success', + sequence: 1496, + session: 'unset', + summary: { + actor: { + primary: 'unset', + }, + object: { + primary: '1', + type: 'audit-config', + }, + }, + }, + cloud: { + instance: { + id: '133555295', + }, + provider: 'digitalocean', + region: 'tor1', + }, + ecs: { + version: '1.0.0-beta2', + }, + ...flattenWithPrefix('event', { + action: 'changed-audit-configuration', + category: 'configuration', + module: 'auditd', + kind: 'signal', + }), + host: { + architecture: 'x86_64', + containerized: false, + hostname: 'suricata-zeek-sensor-toronto', + id: '8cc95778cce5407c809480e8e32ad76b', + name: 'suricata-zeek-sensor-toronto', + os: { + codename: 'bionic', + family: 'debian', + kernel: '4.15.0-45-generic', + name: 'Ubuntu', + platform: 'ubuntu', + version: '18.04.2 LTS (Bionic Beaver)', + }, + }, + service: { + type: 'auditd', + }, + user: { + audit: { + id: 'unset', + }, + }, + [ALERT_REASON]: + 'configuration event on suricata-zeek-sensor-toronto created high alert Signal Testing Query.', + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 1, + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: '9xbRBmkBR346wHgngz2D', + index: 'auditbeat-8.0.0-2019.02.19-000001', + type: 'event', + }, + ], + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + action: 'changed-audit-configuration', + category: 'configuration', + module: 'auditd', + }), + }); + }); + + it('generates up to max_signals for non-sequence EQL queries', async () => { + const maxSignals = 200; + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + max_signals: maxSignals, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 2 }); + expect(previewAlerts.length).eql(maxSignals); + }); + + it('uses the provided event_category_override', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'config_change where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', + event_category_override: 'auditd.message_type', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + const fullSignal = previewAlerts[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + + expect(fullSignal).eql({ + ...fullSignal, + auditd: { + data: { + audit_enabled: '1', + old: '1', + }, + message_type: 'config_change', + result: 'success', + sequence: 1496, + session: 'unset', + summary: { + actor: { + primary: 'unset', + }, + object: { + primary: '1', + type: 'audit-config', + }, + }, + }, + ...flattenWithPrefix('event', { + action: 'changed-audit-configuration', + category: 'configuration', + module: 'auditd', + kind: 'signal', + }), + service: { + type: 'auditd', + }, + user: { + audit: { + id: 'unset', + }, + }, + [ALERT_REASON]: + 'configuration event on suricata-zeek-sensor-toronto created high alert Signal Testing Query.', + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 1, + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: '9xbRBmkBR346wHgngz2D', + index: 'auditbeat-8.0.0-2019.02.19-000001', + type: 'event', + }, + ], + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + action: 'changed-audit-configuration', + category: 'configuration', + module: 'auditd', + }), + }); + }); + + it('uses the provided timestamp_field', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['fake.index.1']), + query: 'any where true', + timestamp_field: 'created_at', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(3); + + const createdAtHits = previewAlerts.map((hit) => hit._source?.created_at).sort(); + expect(createdAtHits).to.eql([1622676785, 1622676790, 1622676795]); + }); + + it('uses the provided tiebreaker_field', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['fake.index.1']), + query: 'any where true', + tiebreaker_field: 'locale', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(3); + + const createdAtHits = previewAlerts.map((hit) => hit._source?.locale); + expect(createdAtHits).to.eql(['es', 'pt', 'ua']); + }); + + it('generates building block signals from EQL sequences in the expected form', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'sequence by host.name [anomoly where true] [any where true]', // TODO: spelling + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const buildingBlock = previewAlerts.find( + (alert) => + alert._source?.[ALERT_DEPTH] === 1 && + get(alert._source, ALERT_ORIGINAL_EVENT_CATEGORY) === 'anomoly' + ); + expect(buildingBlock).not.eql(undefined); + const fullSignal = buildingBlock?._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + + expect(fullSignal).eql({ + ...fullSignal, + agent: { + ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', + hostname: 'zeek-sensor-amsterdam', + id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', + type: 'auditbeat', + version: '8.0.0', + }, + auditd: { + data: { + a0: '3', + a1: '107', + a2: '1', + a3: '7ffc186b58e0', + arch: 'x86_64', + auid: 'unset', + dev: 'eth0', + exit: '0', + gid: '0', + old_prom: '0', + prom: '256', + ses: 'unset', + syscall: 'setsockopt', + tty: '(none)', + uid: '0', + }, + message_type: 'anom_promiscuous', + result: 'success', + sequence: 1392, + session: 'unset', + summary: { + actor: { + primary: 'unset', + secondary: 'root', + }, + how: '/usr/bin/bro', + object: { + primary: 'eth0', + type: 'network-device', + }, + }, + }, + cloud: { instance: { id: '133551048' }, provider: 'digitalocean', region: 'ams3' }, + ecs: { version: '1.0.0-beta2' }, + ...flattenWithPrefix('event', { + action: 'changed-promiscuous-mode-on-device', + category: 'anomoly', + module: 'auditd', + kind: 'signal', + }), + host: { + architecture: 'x86_64', + containerized: false, + hostname: 'zeek-sensor-amsterdam', + id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', + name: 'zeek-sensor-amsterdam', + os: { + codename: 'bionic', + family: 'debian', + kernel: '4.15.0-45-generic', + name: 'Ubuntu', + platform: 'ubuntu', + version: '18.04.2 LTS (Bionic Beaver)', + }, + }, + process: { + executable: '/usr/bin/bro', + name: 'bro', + pid: 30157, + ppid: 30151, + title: + '/usr/bin/bro -i eth0 -U .status -p broctl -p broctl-live -p standalone -p local -p bro local.bro broctl broctl/standalone broctl', + }, + service: { type: 'auditd' }, + user: { + audit: { id: 'unset' }, + effective: { + group: { + id: '0', + name: 'root', + }, + id: '0', + name: 'root', + }, + filesystem: { + group: { + id: '0', + name: 'root', + }, + id: '0', + name: 'root', + }, + group: { id: '0', name: 'root' }, + id: '0', + name: 'root', + saved: { + group: { + id: '0', + name: 'root', + }, + id: '0', + name: 'root', + }, + }, + [ALERT_REASON]: + 'anomoly event with process bro, by root on zeek-sensor-amsterdam created high alert Signal Testing Query.', + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_GROUP_ID]: fullSignal[ALERT_GROUP_ID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 1, + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: 'VhXOBmkBR346wHgnLP8T', + index: 'auditbeat-8.0.0-2019.02.19-000001', + type: 'event', + }, + ], + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + action: 'changed-promiscuous-mode-on-device', + category: 'anomoly', + module: 'auditd', + }), + }); + }); + + it('generates shell signals from EQL sequences in the expected form', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'sequence by host.name [anomoly where true] [any where true]', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const sequenceAlert = previewAlerts.find((alert) => alert._source?.[ALERT_DEPTH] === 2); + const source = sequenceAlert?._source; + if (!source) { + return expect(source).to.be.ok(); + } + const eventIds = (source?.[ALERT_ANCESTORS] as Ancestor[]) + .filter((event) => event.depth === 1) + .map((event) => event.id); + expect(source).eql({ + ...source, + agent: { + ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', + hostname: 'zeek-sensor-amsterdam', + id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', + type: 'auditbeat', + version: '8.0.0', + }, + auditd: { session: 'unset', summary: { actor: { primary: 'unset' } } }, + cloud: { instance: { id: '133551048' }, provider: 'digitalocean', region: 'ams3' }, + ecs: { version: '1.0.0-beta2' }, + [EVENT_KIND]: 'signal', + host: { + architecture: 'x86_64', + containerized: false, + hostname: 'zeek-sensor-amsterdam', + id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', + name: 'zeek-sensor-amsterdam', + os: { + codename: 'bionic', + family: 'debian', + kernel: '4.15.0-45-generic', + name: 'Ubuntu', + platform: 'ubuntu', + version: '18.04.2 LTS (Bionic Beaver)', + }, + }, + service: { type: 'auditd' }, + user: { audit: { id: 'unset' }, id: '0', name: 'root' }, + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 2, + [ALERT_GROUP_ID]: source[ALERT_GROUP_ID], + [ALERT_REASON]: + 'event by root on zeek-sensor-amsterdam created high alert Signal Testing Query.', + [ALERT_RULE_UUID]: source[ALERT_RULE_UUID], + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: 'VhXOBmkBR346wHgnLP8T', + index: 'auditbeat-8.0.0-2019.02.19-000001', + type: 'event', + }, + { + depth: 1, + id: eventIds[0], + index: '', + rule: source[ALERT_RULE_UUID], + type: 'signal', + }, + { + depth: 0, + id: '4hbXBmkBR346wHgn6fdp', + index: 'auditbeat-8.0.0-2019.02.19-000001', + type: 'event', + }, + { + depth: 1, + id: eventIds[1], + index: '', + rule: source[ALERT_RULE_UUID], + type: 'signal', + }, + ], + }); + }); + + it('generates up to max_signals with an EQL rule', async () => { + const maxSignals = 200; + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'sequence by host.name [any where true] [any where true]', + max_signals: maxSignals, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 5 }); + // For EQL rules, max_signals is the maximum number of detected sequences: each sequence has a building block + // alert for each event in the sequence, so max_signals=200 results in 400 building blocks in addition to + // 200 regular alerts + expect(previewAlerts.length).eql(maxSignals * 3); + const shellSignals = previewAlerts.filter((alert) => alert._source?.[ALERT_DEPTH] === 2); + const buildingBlocks = previewAlerts.filter((alert) => alert._source?.[ALERT_DEPTH] === 1); + expect(shellSignals.length).eql(maxSignals); + expect(buildingBlocks.length).eql(maxSignals * 2); + }); + + it('generates signals when an index name contains special characters to encode', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*', '<my-index-{now/d}*>']), + query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + }); + + it('uses the provided filters', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'any where true', + filters: [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'source.ip', + params: { + query: '46.148.18.163', + }, + }, + query: { + match_phrase: { + 'source.ip': '46.148.18.163', + }, + }, + }, + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'event.action', + params: { + query: 'error', + }, + }, + query: { + match_phrase: { + 'event.action': 'error', + }, + }, + }, + ], + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(2); + }); + + describe('with host risk index', async () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + it('should be enriched with host risk score', async () => { + const rule: EqlRuleCreateProps = { + ...getEqlRuleForSignalTesting(['auditbeat-*']), + query: 'configuration where agent.id=="a1d7b39c-f898-4dbe-a761-efb61939302d"', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + const fullSignal = previewAlerts[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + expect(fullSignal?.host?.risk?.calculated_level).to.eql('Critical'); + expect(fullSignal?.host?.risk?.calculated_score_norm).to.eql(96); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/index.ts new file mode 100644 index 0000000000000..547e3a4706e3c --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/index.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 { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('detection engine api security and spaces enabled - rule execution logic', function () { + loadTestFile(require.resolve('./eql')); + loadTestFile(require.resolve('./machine_learning')); + loadTestFile(require.resolve('./new_terms')); + loadTestFile(require.resolve('./query')); + loadTestFile(require.resolve('./saved_query')); + loadTestFile(require.resolve('./threat_match')); + loadTestFile(require.resolve('./threshold')); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_ml.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts similarity index 68% rename from x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_ml.ts rename to x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts index a29963ec3b6cb..0949d8255bed2 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_ml.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts @@ -17,7 +17,7 @@ import { SPACE_IDS, VERSION, } from '@kbn/rule-data-utils'; -import { MachineLearningCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { MachineLearningRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { ALERT_ANCESTORS, ALERT_DEPTH, @@ -33,9 +33,14 @@ import { import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createRule, - createRuleWithExceptionEntries, deleteAllAlerts, + deleteSignalsIndex, + executeSetupModuleRequest, + forceStartDatafeeds, getOpenSignals, + getPreviewAlerts, + previewRule, + previewRuleWithExceptionEntries, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -47,7 +52,7 @@ export default ({ getService }: FtrProviderContext) => { const siemModule = 'security_linux_v3'; const mlJobId = 'v3_linux_anomalous_network_activity'; - const testRule: MachineLearningCreateSchema = { + const rule: MachineLearningRuleCreateProps = { name: 'Test ML rule', description: 'Test ML rule description', risk_score: 50, @@ -56,63 +61,32 @@ export default ({ getService }: FtrProviderContext) => { anomaly_threshold: 30, machine_learning_job_id: mlJobId, from: '1900-01-01T00:00:00.000Z', + rule_id: 'ml-rule-id', }; - async function executeSetupModuleRequest(module: string, rspCode: number) { - const { body } = await supertest - .post(`/api/ml/modules/setup/${module}`) - .set('kbn-xsrf', 'true') - .send({ - prefix: '', - groups: ['auditbeat'], - indexPatternName: 'auditbeat-*', - startDatafeed: false, - useDedicatedIndex: true, - applyToAllSpaces: true, - }) - .expect(rspCode); - - return body; - } - - async function forceStartDatafeeds(jobId: string, rspCode: number) { - const { body } = await supertest - .post(`/api/ml/jobs/force_start_datafeeds`) - .set('kbn-xsrf', 'true') - .send({ - datafeedIds: [`datafeed-${jobId}`], - start: new Date().getUTCMilliseconds(), - }) - .expect(rspCode); - - return body; - } - - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/125033 // FLAKY: https://github.com/elastic/kibana/issues/142993 - describe.skip('Generating signals from ml anomalies', () => { + describe.skip('Machine learning type rules', () => { before(async () => { // Order is critical here: auditbeat data must be loaded before attempting to start the ML job, // as the job looks for certain indices on start await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); - await executeSetupModuleRequest(siemModule, 200); - await forceStartDatafeeds(mlJobId, 200); + await executeSetupModuleRequest({ module: siemModule, rspCode: 200, supertest }); + await forceStartDatafeeds({ jobId: mlJobId, rspCode: 200, supertest }); await esArchiver.load('x-pack/test/functional/es_archives/security_solution/anomalies'); }); after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/anomalies'); - }); - - afterEach(async () => { + await deleteSignalsIndex(supertest, log); await deleteAllAlerts(supertest, log); }); + // First test creates a real rule - remaining tests use preview API it('should create 1 alert from ML rule when record meets anomaly_threshold', async () => { - const createdRule = await createRule(supertest, log, testRule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toBe(1); - const signal = signalsOpen.hits.hits[0]; + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + expect(alerts.hits.hits.length).toBe(1); + const signal = alerts.hits.hits[0]; expect(signal._source).toEqual( expect.objectContaining({ @@ -162,7 +136,7 @@ export default ({ getService }: FtrProviderContext) => { required_fields: [], risk_score: 50, risk_score_mapping: [], - rule_id: createdRule.rule_id, + rule_id: 'ml-rule-id', setup: '', severity: 'critical', severity_mapping: [], @@ -185,13 +159,12 @@ export default ({ getService }: FtrProviderContext) => { }); it('should create 7 alerts from ML rule when records meet anomaly_threshold', async () => { - const rule: MachineLearningCreateSchema = { - ...testRule, - anomaly_threshold: 20, - }; - const createdRule = await createRule(supertest, log, rule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toBe(7); + const { previewId } = await previewRule({ + supertest, + rule: { ...rule, anomaly_threshold: 20 }, + }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBe(7); }); describe('with non-value list exception', () => { @@ -199,18 +172,23 @@ export default ({ getService }: FtrProviderContext) => { await deleteAllExceptions(supertest, log); }); it('generates no signals when an exception is added for an ML rule', async () => { - const createdRule = await createRuleWithExceptionEntries(supertest, log, testRule, [ - [ - { - field: 'host.name', - operator: 'included', - type: 'match', - value: 'mothra', - }, + const { previewId } = await previewRuleWithExceptionEntries({ + supertest, + log, + rule, + entries: [ + [ + { + field: 'host.name', + operator: 'included', + type: 'match', + value: 'mothra', + }, + ], ], - ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toBe(0); + }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBe(0); }); }); @@ -227,21 +205,26 @@ export default ({ getService }: FtrProviderContext) => { it('generates no signals when a value list exception is added for an ML rule', async () => { const valueListId = 'value-list-id'; await importFile(supertest, log, 'keyword', ['mothra'], valueListId); - const createdRule = await createRuleWithExceptionEntries(supertest, log, testRule, [ - [ - { - field: 'host.name', - operator: 'included', - type: 'list', - list: { - id: valueListId, - type: 'keyword', + const { previewId } = await previewRuleWithExceptionEntries({ + supertest, + log, + rule, + entries: [ + [ + { + field: 'host.name', + operator: 'included', + type: 'list', + list: { + id: valueListId, + type: 'keyword', + }, }, - }, + ], ], - ]); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toBe(0); + }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBe(0); }); }); @@ -255,10 +238,10 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be enriched with host risk score', async () => { - const createdRule = await createRule(supertest, log, testRule); - const signalsOpen = await getOpenSignals(supertest, log, es, createdRule); - expect(signalsOpen.hits.hits.length).toBe(1); - const fullSignal = signalsOpen.hits.hits[0]._source; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBe(1); + const fullSignal = previewAlerts[0]._source; expect(fullSignal?.host?.risk?.calculated_level).toBe('Low'); expect(fullSignal?.host?.risk?.calculated_score_norm).toBe(1); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts new file mode 100644 index 0000000000000..4bfbe92118599 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts @@ -0,0 +1,385 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { NewTermsRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; +import { orderBy } from 'lodash'; +import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema/mocks'; +import { DetectionAlert } from '@kbn/security-solution-plugin/common/detection_engine/schemas/alerts'; +import { + createRule, + deleteAllAlerts, + deleteSignalsIndex, + getOpenSignals, + getPreviewAlerts, + previewRule, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { previewRuleWithExceptionEntries } from '../../utils/preview_rule_with_exception_entries'; +import { deleteAllExceptions } from '../../../lists_api_integration/utils'; + +const removeRandomValuedProperties = (alert: DetectionAlert | undefined) => { + if (!alert) { + return undefined; + } + const { + 'kibana.version': version, + 'kibana.alert.rule.execution.uuid': execUuid, + 'kibana.alert.rule.uuid': uuid, + '@timestamp': timestamp, + 'kibana.alert.rule.created_at': createdAt, + 'kibana.alert.rule.updated_at': updatedAt, + 'kibana.alert.uuid': alertUuid, + ...restOfAlert + } = alert; + return restOfAlert; +}; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('New terms type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + // First test creates a real rule - remaining tests use preview API + + // This test also tests that alerts are NOT created for terms that are not new: the host name + // suricata-sensor-san-francisco appears in a document at 2019-02-19T20:42:08.230Z, but also appears + // in earlier documents so is not new. An alert should not be generated for that term. + it('should generate 1 alert with 1 selected field', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + from: '2019-02-19T20:42:00.000Z', + history_window_start: '2019-01-19T20:42:00.000Z', + }; + + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + + expect(alerts.hits.hits.length).eql(1); + expect(removeRandomValuedProperties(alerts.hits.hits[0]._source)).eql({ + 'kibana.alert.new_terms': ['zeek-newyork-sha-aa8df15'], + 'kibana.alert.rule.category': 'New Terms Rule', + 'kibana.alert.rule.consumer': 'siem', + 'kibana.alert.rule.name': 'Query with a rule id', + 'kibana.alert.rule.producer': 'siem', + 'kibana.alert.rule.rule_type_id': 'siem.newTermsRule', + 'kibana.space_ids': ['default'], + 'kibana.alert.rule.tags': [], + agent: { + ephemeral_id: '7cc2091a-72f1-4c63-843b-fdeb622f9c69', + hostname: 'zeek-newyork-sha-aa8df15', + id: '4b4462ef-93d2-409c-87a6-299d942e5047', + type: 'auditbeat', + version: '8.0.0', + }, + cloud: { instance: { id: '139865230' }, provider: 'digitalocean', region: 'nyc1' }, + ecs: { version: '1.0.0-beta2' }, + host: { + architecture: 'x86_64', + hostname: 'zeek-newyork-sha-aa8df15', + id: '3729d06ce9964aa98549f41cbd99334d', + ip: ['157.230.208.30', '10.10.0.6', 'fe80::24ce:f7ff:fede:a571'], + mac: ['26:ce:f7:de:a5:71'], + name: 'zeek-newyork-sha-aa8df15', + os: { + codename: 'cosmic', + family: 'debian', + kernel: '4.18.0-10-generic', + name: 'Ubuntu', + platform: 'ubuntu', + version: '18.10 (Cosmic Cuttlefish)', + }, + }, + message: + 'Login by user root (UID: 0) on pts/0 (PID: 20638) from 8.42.77.171 (IP: 8.42.77.171)', + process: { pid: 20638 }, + service: { type: 'system' }, + source: { ip: '8.42.77.171' }, + user: { id: 0, name: 'root', terminal: 'pts/0' }, + 'event.action': 'user_login', + 'event.category': 'authentication', + 'event.dataset': 'login', + 'event.kind': 'signal', + 'event.module': 'system', + 'event.origin': '/var/log/wtmp', + 'event.outcome': 'success', + 'event.type': 'authentication_success', + 'kibana.alert.original_time': '2019-02-19T20:42:08.230Z', + 'kibana.alert.ancestors': [ + { + id: 'x07wJ2oB9v5HJNSHhyxi', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + 'kibana.alert.status': 'active', + 'kibana.alert.workflow_status': 'open', + 'kibana.alert.depth': 1, + 'kibana.alert.reason': + 'authentication event with source 8.42.77.171 by root on zeek-newyork-sha-aa8df15 created high alert Query with a rule id.', + 'kibana.alert.severity': 'high', + 'kibana.alert.risk_score': 55, + 'kibana.alert.rule.parameters': { + description: 'Detecting root and admin users', + risk_score: 55, + severity: 'high', + author: [], + false_positives: [], + from: '2019-02-19T20:42:00.000Z', + rule_id: 'rule-1', + max_signals: 100, + risk_score_mapping: [], + severity_mapping: [], + threat: [], + to: 'now', + references: [], + version: 1, + exceptions_list: [], + immutable: false, + related_integrations: [], + required_fields: [], + setup: '', + type: 'new_terms', + query: '*', + new_terms_fields: ['host.name'], + history_window_start: '2019-01-19T20:42:00.000Z', + index: ['auditbeat-*'], + language: 'kuery', + }, + 'kibana.alert.rule.actions': [], + 'kibana.alert.rule.author': [], + 'kibana.alert.rule.created_by': 'elastic', + 'kibana.alert.rule.description': 'Detecting root and admin users', + 'kibana.alert.rule.enabled': true, + 'kibana.alert.rule.exceptions_list': [], + 'kibana.alert.rule.false_positives': [], + 'kibana.alert.rule.from': '2019-02-19T20:42:00.000Z', + 'kibana.alert.rule.immutable': false, + 'kibana.alert.rule.indices': ['auditbeat-*'], + 'kibana.alert.rule.interval': '5m', + 'kibana.alert.rule.max_signals': 100, + 'kibana.alert.rule.references': [], + 'kibana.alert.rule.risk_score_mapping': [], + 'kibana.alert.rule.rule_id': 'rule-1', + 'kibana.alert.rule.severity_mapping': [], + 'kibana.alert.rule.threat': [], + 'kibana.alert.rule.to': 'now', + 'kibana.alert.rule.type': 'new_terms', + 'kibana.alert.rule.updated_by': 'elastic', + 'kibana.alert.rule.version': 1, + 'kibana.alert.rule.risk_score': 55, + 'kibana.alert.rule.severity': 'high', + 'kibana.alert.original_event.action': 'user_login', + 'kibana.alert.original_event.category': 'authentication', + 'kibana.alert.original_event.dataset': 'login', + 'kibana.alert.original_event.kind': 'event', + 'kibana.alert.original_event.module': 'system', + 'kibana.alert.original_event.origin': '/var/log/wtmp', + 'kibana.alert.original_event.outcome': 'success', + 'kibana.alert.original_event.type': 'authentication_success', + }); + }); + + it('should generate 3 alerts when 1 document has 3 new values', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + from: '2019-02-19T20:42:00.000Z', + history_window_start: '2019-01-19T20:42:00.000Z', + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).eql(3); + const previewAlertsOrderedByHostIp = orderBy( + previewAlerts, + '_source.kibana.alert.new_terms', + 'asc' + ); + expect(previewAlertsOrderedByHostIp[0]._source?.['kibana.alert.new_terms']).eql([ + '10.10.0.6', + ]); + expect(previewAlertsOrderedByHostIp[1]._source?.['kibana.alert.new_terms']).eql([ + '157.230.208.30', + ]); + expect(previewAlertsOrderedByHostIp[2]._source?.['kibana.alert.new_terms']).eql([ + 'fe80::24ce:f7ff:fede:a571', + ]); + }); + + it('should generate alerts for every term when history window is small', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + from: '2019-02-19T20:42:00.000Z', + // Set the history_window_start close to 'from' so we should alert on all terms in the time range + history_window_start: '2019-02-19T20:41:59.000Z', + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).eql(5); + const hostNames = previewAlerts + .map((signal) => signal._source?.['kibana.alert.new_terms']) + .sort(); + expect(hostNames[0]).eql(['suricata-sensor-amsterdam']); + expect(hostNames[1]).eql(['suricata-sensor-san-francisco']); + expect(hostNames[2]).eql(['zeek-newyork-sha-aa8df15']); + expect(hostNames[3]).eql(['zeek-sensor-amsterdam']); + expect(hostNames[4]).eql(['zeek-sensor-san-francisco']); + }); + + describe('timestamp override and fallback', () => { + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' + ); + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/timestamp_override_3' + ); + }); + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' + ); + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/timestamp_override_3' + ); + }); + + it('should generate the correct alerts', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + // myfakeindex-3 does not have event.ingested mapped so we can test if the runtime field + // 'kibana.combined_timestamp' handles unmapped fields properly + index: ['timestamp-fallback-test', 'myfakeindex-3'], + new_terms_fields: ['host.name'], + from: '2020-12-16T16:00:00.000Z', + // Set the history_window_start close to 'from' so we should alert on all terms in the time range + history_window_start: '2020-12-16T15:59:00.000Z', + timestamp_override: 'event.ingested', + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).eql(2); + const hostNames = previewAlerts + .map((signal) => signal._source?.['kibana.alert.new_terms']) + .sort(); + expect(hostNames[0]).eql(['host-3']); + expect(hostNames[1]).eql(['host-4']); + }); + }); + + describe('with exceptions', async () => { + afterEach(async () => { + await deleteAllExceptions(supertest, log); + }); + + it('should apply exceptions', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + from: '2019-02-19T20:42:00.000Z', + // Set the history_window_start close to 'from' so we should alert on all terms in the time range + history_window_start: '2019-02-19T20:41:59.000Z', + }; + + const { previewId } = await previewRuleWithExceptionEntries({ + supertest, + log, + rule, + entries: [ + [ + { + field: 'host.name', + operator: 'included', + type: 'match', + value: 'zeek-sensor-san-francisco', + }, + ], + ], + }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).eql(4); + const hostNames = previewAlerts + .map((signal) => signal._source?.['kibana.alert.new_terms']) + .sort(); + expect(hostNames[0]).eql(['suricata-sensor-amsterdam']); + expect(hostNames[1]).eql(['suricata-sensor-san-francisco']); + expect(hostNames[2]).eql(['zeek-newyork-sha-aa8df15']); + expect(hostNames[3]).eql(['zeek-sensor-amsterdam']); + }); + }); + + it('should work for max signals > 100', async () => { + const maxSignals = 200; + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['process.pid'], + from: '2018-02-19T20:42:00.000Z', + // Set the history_window_start close to 'from' so we should alert on all terms in the time range + history_window_start: '2018-02-19T20:41:59.000Z', + max_signals: maxSignals, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 2 }); + + expect(previewAlerts.length).eql(maxSignals); + const processPids = previewAlerts + .map((signal) => signal._source?.['kibana.alert.new_terms']) + .sort(); + expect(processPids[0]).eql([1]); + }); + + describe('alerts should be be enriched', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + it('should be enriched with host risk score', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + from: '2019-02-19T20:42:00.000Z', + history_window_start: '2019-01-19T20:42:00.000Z', + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts[0]?._source?.host?.risk?.calculated_level).to.eql('Low'); + expect(previewAlerts[0]?._source?.host?.risk?.calculated_score_norm).to.eql(23); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts new file mode 100644 index 0000000000000..8090e4d2ce709 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/query.ts @@ -0,0 +1,426 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { + ALERT_RISK_SCORE, + ALERT_RULE_PARAMETERS, + ALERT_RULE_RULE_ID, + ALERT_SEVERITY, + ALERT_WORKFLOW_STATUS, +} from '@kbn/rule-data-utils'; +import { flattenWithPrefix } from '@kbn/securitysolution-rules'; + +import { orderBy } from 'lodash'; + +import { QueryRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; +import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; +import { + ALERT_ANCESTORS, + ALERT_DEPTH, + ALERT_ORIGINAL_TIME, + ALERT_ORIGINAL_EVENT, +} from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { + createRule, + deleteAllAlerts, + deleteSignalsIndex, + getOpenSignals, + getPreviewAlerts, + getRuleForSignalTesting, + getSimpleRule, + previewRule, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +/** + * Specific _id to use for some of the tests. If the archiver changes and you see errors + * here, update this to a new value of a chosen auditbeat record and update the tests values. + */ +const ID = 'BhbXBmkBR346wHgn4PeZ'; + +/** + * Test coverage: + * [x] - Happy path generating 1 alert + * [x] - Rule type respects max signals + * [x] - Alerts on alerts + */ + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('Query type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.load('x-pack/test/functional/es_archives/security_solution/alerts/8.1.0'); + await esArchiver.load('x-pack/test/functional/es_archives/signals/severity_risk_overrides'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/alerts/8.1.0'); + await esArchiver.unload('x-pack/test/functional/es_archives/signals/severity_risk_overrides'); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + // First test creates a real rule - remaining tests use preview API + it('should have the specific audit record for _id or none of these tests below will pass', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + expect(alerts.hits.hits.length).greaterThan(0); + expect(alerts.hits.hits[0]._source?.['kibana.alert.ancestors'][0].id).eql(ID); + }); + + it('should abide by max_signals > 100', async () => { + const maxSignals = 200; + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + max_signals: maxSignals, + }; + const { previewId } = await previewRule({ supertest, rule }); + // Search for 2x max_signals to make sure we aren't making more than max_signals + const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxSignals * 2 }); + expect(previewAlerts.length).equal(maxSignals); + }); + + it('should have recorded the rule_id within the signal', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts[0]._source?.[ALERT_RULE_RULE_ID]).eql(getSimpleRule().rule_id); + }); + + it('should query and get back expected signal structure using a basic KQL query', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const signal = previewAlerts[0]._source; + + expect(signal).eql({ + ...signal, + [ALERT_ANCESTORS]: [ + { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 1, + [ALERT_ORIGINAL_TIME]: '2019-02-19T17:40:03.790Z', + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + action: 'socket_closed', + dataset: 'socket', + kind: 'event', + module: 'system', + }), + }); + }); + + it('should query and get back expected signal structure when it is a signal on a signal', async () => { + const alertId = '30a75fe46d3dbdfab55982036f77a8d60e2d1112e96f277c3b8c22f9bb57817a'; + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting([`.alerts-security.alerts-default*`]), + rule_id: 'signal-on-signal', + query: `_id:${alertId}`, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).to.eql(1); + + const signal = previewAlerts[0]._source; + + if (!signal) { + return expect(signal).to.be.ok(); + } + + expect(signal).eql({ + ...signal, + [ALERT_ANCESTORS]: [ + { + id: 'ahEToH8BK09aFtXZFVMq', + type: 'event', + index: 'events-index-000001', + depth: 0, + }, + { + rule: '031d5c00-a72f-11ec-a8a3-7b1c8077fc3e', + id: '30a75fe46d3dbdfab55982036f77a8d60e2d1112e96f277c3b8c22f9bb57817a', + type: 'signal', + index: '.internal.alerts-security.alerts-default-000001', + depth: 1, + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 2, + [ALERT_ORIGINAL_TIME]: '2022-03-19T02:48:12.634Z', + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + agent_id_status: 'verified', + ingested: '2022-03-19T02:47:57.376Z', + dataset: 'elastic_agent.filebeat', + }), + }); + }); + + it('should not have risk score fields without risk indices', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts[0]?._source?.host?.risk).to.eql(undefined); + expect(previewAlerts[0]?._source?.user?.risk).to.eql(undefined); + }); + + describe('with host risk index', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + it('should host have risk score field and do not have user risk score', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID} or _id:GBbXBmkBR346wHgn5_eR or _id:x10zJ2oE9v5HJNSHhyxi`, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + const firstAlert = previewAlerts.find( + (alert) => alert?._source?.host?.name === 'suricata-zeek-sensor-toronto' + ); + const secondAlert = previewAlerts.find( + (alert) => alert?._source?.host?.name === 'suricata-sensor-london' + ); + const thirdAlert = previewAlerts.find( + (alert) => alert?._source?.host?.name === 'IE11WIN8_1' + ); + + expect(firstAlert?._source?.host?.risk?.calculated_level).to.eql('Critical'); + expect(firstAlert?._source?.host?.risk?.calculated_score_norm).to.eql(96); + expect(firstAlert?._source?.user?.risk).to.eql(undefined); + expect(secondAlert?._source?.host?.risk?.calculated_level).to.eql('Low'); + expect(secondAlert?._source?.host?.risk?.calculated_score_norm).to.eql(20); + expect(thirdAlert?._source?.host?.risk).to.eql(undefined); + }); + }); + + describe('with host and user risk indices', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + await esArchiver.load('x-pack/test/functional/es_archives/entity/user_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + await esArchiver.unload('x-pack/test/functional/es_archives/entity/user_risk'); + }); + + it('should have host and user risk score fields', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts[0]?._source?.host?.risk?.calculated_level).to.eql('Critical'); + expect(previewAlerts[0]?._source?.host?.risk?.calculated_score_norm).to.eql(96); + expect(previewAlerts[0]?._source?.user?.risk?.calculated_level).to.eql('Low'); + expect(previewAlerts[0]?._source?.user?.risk?.calculated_score_norm).to.eql(11); + }); + }); + + /** + * Here we test the functionality of Severity and Risk Score overrides (also called "mappings" + * in the code). If the rule specifies a mapping, then the final Severity or Risk Score + * value of the signal will be taken from the mapped field of the source event. + */ + it('should get default severity and risk score if there is no mapping', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['signal_overrides']), + severity: 'medium', + risk_score: 75, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts.length).equal(4); + previewAlerts.forEach((alert) => { + expect(alert._source?.[ALERT_SEVERITY]).equal('medium'); + expect(alert._source?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([]); + + expect(alert._source?.[ALERT_RISK_SCORE]).equal(75); + expect(alert._source?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([]); + }); + }); + + it('should get overridden severity if the rule has a mapping for it', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['signal_overrides']), + severity: 'medium', + severity_mapping: [ + { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, + { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, + ], + risk_score: 75, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const alertsOrderedByParentId = orderBy(previewAlerts, 'signal.parent.id', 'asc'); + const severities = alertsOrderedByParentId.map((alert) => ({ + id: (alert._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id, + value: alert._source?.[ALERT_SEVERITY], + })); + + expect(alertsOrderedByParentId.length).equal(4); + expect(severities).eql([ + { id: '1', value: 'high' }, + { id: '2', value: 'critical' }, + { id: '3', value: 'critical' }, + { id: '4', value: 'critical' }, + ]); + + alertsOrderedByParentId.forEach((alert) => { + expect(alert._source?.[ALERT_RISK_SCORE]).equal(75); + expect(alert._source?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([]); + expect(alert._source?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([ + { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, + { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, + ]); + }); + }); + + it('should get overridden risk score if the rule has a mapping for it', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['signal_overrides']), + severity: 'medium', + risk_score: 75, + risk_score_mapping: [ + { field: 'my_risk', operator: 'equals', value: '', risk_score: undefined }, + ], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const alertsOrderedByParentId = orderBy(previewAlerts, 'signal.parent.id', 'asc'); + const riskScores = alertsOrderedByParentId.map((alert) => ({ + id: (alert._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id, + value: alert._source?.[ALERT_RISK_SCORE], + })); + + expect(alertsOrderedByParentId.length).equal(4); + expect(riskScores).eql([ + { id: '1', value: 31.14 }, + { id: '2', value: 32.14 }, + { id: '3', value: 33.14 }, + { id: '4', value: 34.14 }, + ]); + + alertsOrderedByParentId.forEach((alert) => { + expect(alert._source?.[ALERT_SEVERITY]).equal('medium'); + expect(alert._source?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([]); + expect(alert._source?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([ + { field: 'my_risk', operator: 'equals', value: '' }, + ]); + }); + }); + + it('should get overridden severity and risk score if the rule has both mappings', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['signal_overrides']), + severity: 'medium', + severity_mapping: [ + { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, + { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, + ], + risk_score: 75, + risk_score_mapping: [ + { field: 'my_risk', operator: 'equals', value: '', risk_score: undefined }, + ], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const alertsOrderedByParentId = orderBy(previewAlerts, 'signal.parent.id', 'asc'); + const values = alertsOrderedByParentId.map((alert) => ({ + id: (alert._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id, + severity: alert._source?.[ALERT_SEVERITY], + risk: alert._source?.[ALERT_RISK_SCORE], + })); + + expect(alertsOrderedByParentId.length).equal(4); + expect(values).eql([ + { id: '1', severity: 'high', risk: 31.14 }, + { id: '2', severity: 'critical', risk: 32.14 }, + { id: '3', severity: 'critical', risk: 33.14 }, + { id: '4', severity: 'critical', risk: 34.14 }, + ]); + + alertsOrderedByParentId.forEach((alert) => { + expect(alert._source?.[ALERT_RULE_PARAMETERS].severity_mapping).eql([ + { field: 'my_severity', operator: 'equals', value: 'sev_900', severity: 'high' }, + { field: 'my_severity', operator: 'equals', value: 'sev_max', severity: 'critical' }, + ]); + expect(alert._source?.[ALERT_RULE_PARAMETERS].risk_score_mapping).eql([ + { field: 'my_risk', operator: 'equals', value: '' }, + ]); + }); + }); + + it('should generate signals with name_override field', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `event.action:boot`, + rule_name_override: 'event.action', + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const fullSignal = previewAlerts[0]; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + + expect(previewAlerts[0]._source?.['kibana.alert.rule.name']).to.eql('boot'); + }); + + it('should not generate duplicate signals', async () => { + const rule: QueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + query: `_id:${ID}`, + }; + + const { previewId } = await previewRule({ supertest, rule, invocationCount: 2 }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).to.eql(1); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/saved_query.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/saved_query.ts new file mode 100644 index 0000000000000..c6d26e994a99d --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/saved_query.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; +import { flattenWithPrefix } from '@kbn/securitysolution-rules'; + +import { SavedQueryRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; +import { + ALERT_ANCESTORS, + ALERT_DEPTH, + ALERT_ORIGINAL_TIME, + ALERT_ORIGINAL_EVENT, +} from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { + createRule, + deleteAllAlerts, + deleteSignalsIndex, + getOpenSignals, + getRuleForSignalTesting, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +/** + * Specific _id to use for some of the tests. If the archiver changes and you see errors + * here, update this to a new value of a chosen auditbeat record and update the tests values. + */ +const ID = 'BhbXBmkBR346wHgn4PeZ'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('Saved query type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + // First test creates a real rule - remaining tests use preview API + it('should query and get back expected signal structure using a saved query rule', async () => { + const rule: SavedQueryRuleCreateProps = { + ...getRuleForSignalTesting(['auditbeat-*']), + type: 'saved_query', + query: `_id:${ID}`, + saved_id: 'doesnt-exist', + }; + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + const signal = alerts.hits.hits[0]._source; + expect(signal).eql({ + ...signal, + [ALERT_ANCESTORS]: [ + { + id: 'BhbXBmkBR346wHgn4PeZ', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_DEPTH]: 1, + [ALERT_ORIGINAL_TIME]: '2019-02-19T17:40:03.790Z', + ...flattenWithPrefix(ALERT_ORIGINAL_EVENT, { + action: 'socket_closed', + dataset: 'socket', + kind: 'event', + module: 'system', + }), + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts new file mode 100644 index 0000000000000..dfa1f81f6c5d2 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threat_match.ts @@ -0,0 +1,1306 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, isEqual } from 'lodash'; +import expect from '@kbn/expect'; +import { + ALERT_REASON, + ALERT_RULE_UUID, + ALERT_STATUS, + ALERT_RULE_NAMESPACE, + ALERT_RULE_UPDATED_AT, + ALERT_UUID, + ALERT_WORKFLOW_STATUS, + SPACE_IDS, + VERSION, +} from '@kbn/rule-data-utils'; +import { flattenWithPrefix } from '@kbn/securitysolution-rules'; + +import { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; + +import { ENRICHMENT_TYPES } from '@kbn/security-solution-plugin/common/cti/constants'; +import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; +import { + ALERT_ANCESTORS, + ALERT_DEPTH, + ALERT_ORIGINAL_EVENT_ACTION, + ALERT_ORIGINAL_EVENT_CATEGORY, + ALERT_ORIGINAL_EVENT_MODULE, + ALERT_ORIGINAL_TIME, +} from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { + previewRule, + getOpenSignals, + getPreviewAlerts, + deleteSignalsIndex, + deleteAllAlerts, + createRule, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +const format = (value: unknown): string => JSON.stringify(value, null, 2); + +// Asserts that each expected value is included in the subject, independent of +// ordering. Uses _.isEqual for value comparison. +const assertContains = (subject: unknown[], expected: unknown[]) => + expected.forEach((expectedValue) => + expect(subject.some((value) => isEqual(value, expectedValue))).to.eql( + true, + `expected ${format(subject)} to contain ${format(expectedValue)}` + ) + ); + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + const es = getService('es'); + const log = getService('log'); + + /** + * Specific api integration tests for threat matching rule type + */ + describe('Threat match type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + // First test creates a real rule - remaining tests use preview API + it('should be able to execute and get 10 signals when doing a specific query', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + expect(alerts.hits.hits.length).equal(10); + const fullSource = alerts.hits.hits.find( + (signal) => + (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' + ); + const fullSignal = fullSource?._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + expect(fullSignal).eql({ + ...fullSignal, + '@timestamp': fullSignal['@timestamp'], + agent: { + ephemeral_id: '1b4978a0-48be-49b1-ac96-323425b389ab', + hostname: 'zeek-sensor-amsterdam', + id: 'e52588e6-7aa3-4c89-a2c4-d6bc5c286db1', + type: 'auditbeat', + version: '8.0.0', + }, + auditd: { + data: { + hostname: '46.101.47.213', + op: 'PAM:bad_ident', + terminal: 'ssh', + }, + message_type: 'user_err', + result: 'fail', + sequence: 2267, + session: 'unset', + summary: { + actor: { + primary: 'unset', + secondary: 'root', + }, + how: '/usr/sbin/sshd', + object: { + primary: 'ssh', + secondary: '46.101.47.213', + type: 'user-session', + }, + }, + }, + cloud: { + instance: { + id: '133551048', + }, + provider: 'digitalocean', + region: 'ams3', + }, + ecs: { + version: '1.0.0-beta2', + }, + ...flattenWithPrefix('event', { + action: 'error', + category: 'user-login', + module: 'auditd', + kind: 'signal', + }), + host: { + architecture: 'x86_64', + containerized: false, + hostname: 'zeek-sensor-amsterdam', + id: '2ce8b1e7d69e4a1d9c6bcddc473da9d9', + name: 'zeek-sensor-amsterdam', + os: { + codename: 'bionic', + family: 'debian', + kernel: '4.15.0-45-generic', + name: 'Ubuntu', + platform: 'ubuntu', + version: '18.04.2 LTS (Bionic Beaver)', + }, + }, + network: { + direction: 'incoming', + }, + process: { + executable: '/usr/sbin/sshd', + pid: 32739, + }, + service: { + type: 'auditd', + }, + source: { + ip: '46.101.47.213', + }, + user: { + audit: { + id: 'unset', + }, + id: '0', + name: 'root', + }, + [ALERT_ANCESTORS]: [ + { + id: '7yJ-B2kBR346wHgnhlMn', + type: 'event', + index: 'auditbeat-8.0.0-2019.02.19-000001', + depth: 0, + }, + ], + [ALERT_DEPTH]: 1, + [ALERT_ORIGINAL_EVENT_ACTION]: 'error', + [ALERT_ORIGINAL_EVENT_CATEGORY]: 'user-login', + [ALERT_ORIGINAL_EVENT_MODULE]: 'auditd', + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_REASON]: + 'user-login event with source 46.101.47.213 by root on zeek-sensor-amsterdam created high alert Query with a rule id.', + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_STATUS]: 'active', + [ALERT_UUID]: fullSignal[ALERT_UUID], + [ALERT_WORKFLOW_STATUS]: 'open', + [SPACE_IDS]: ['default'], + [VERSION]: fullSignal[VERSION], + threat: { + enrichments: get(fullSignal, 'threat.enrichments'), + }, + ...flattenWithPrefix(ALERT_RULE_NAMESPACE, { + actions: [], + author: [], + category: 'Indicator Match Rule', + consumer: 'siem', + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + exceptions_list: [], + false_positives: [], + from: '1900-01-01T00:00:00.000Z', + immutable: false, + interval: '5m', + max_signals: 100, + name: 'Query with a rule id', + producer: 'siem', + references: [], + risk_score: 55, + risk_score_mapping: [], + rule_type_id: 'siem.indicatorRule', + severity: 'high', + severity_mapping: [], + tags: [], + threat: [], + to: 'now', + type: 'threat_match', + updated_at: fullSignal[ALERT_RULE_UPDATED_AT], + updated_by: 'elastic', + uuid: fullSignal[ALERT_RULE_UUID], + version: 1, + }), + }); + }); + + it('should return 0 matches if the mapping does not match against anything in the mapping', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'invalid.mapping.value', // invalid mapping value + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(0); + }); + + it('should return 0 signals when using an AND and one of the clauses does not have data', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + { + entries: [ + { + field: 'source.ip', + value: 'source.ip', + type: 'mapping', + }, + { + field: 'source.ip', + value: 'destination.ip', // All records from the threat query do NOT have destination.ip, so those records that do not should drop this entire AND clause. + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(0); + }); + + it('should return 0 signals when using an AND and one of the clauses has a made up value that does not exist', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + type: 'threat_match', + index: ['auditbeat-*'], + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + { + entries: [ + { + field: 'source.ip', + value: 'source.ip', + type: 'mapping', + }, + { + field: 'source.ip', + value: 'made.up.non.existent.field', // made up field should not match + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(0); + }); + + describe('timeout behavior', () => { + // TODO: unskip this and see if we can make it not flaky + it.skip('will return an error if a rule execution exceeds the rule interval', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a short interval', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: '*:*', // broad query to take more time + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + concurrent_searches: 1, + interval: '1s', // short interval + items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow + }; + + const { logs } = await previewRule({ supertest, rule }); + expect(logs[0].errors[0]).to.contain('execution has exceeded its allotted interval'); + }); + }); + + describe('indicator enrichment: threat-first search', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/filebeat/threat_intel'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/threat_intel'); + }); + + it('enriches signals with the single indicator that matched', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', // narrow events down to 2 with a destination.ip + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(2); + + const threats = previewAlerts.map((hit) => hit._source?.threat); + expect(threats).to.eql([ + { + enrichments: [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + type: 'url', + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ], + }, + { + enrichments: [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + type: 'url', + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ], + }, + ]); + }); + + it('enriches signals with multiple indicators if several matched', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(1); + + const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threat.enrichments, [ + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + provider: 'other_provider', + type: 'ip', + }, + + matched: { + atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + + it('adds a single indicator that matched multiple fields', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.port: 57324 or threat.indicator.ip:45.115.45.3', // narrow our query to a single indicator + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + ], + }, + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(1); + + const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threat.enrichments, [ + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + // We do not merge matched indicators during enrichment, so in + // certain circumstances a given indicator document could appear + // multiple times in an enriched alert (albeit with different + // threat.indicator.matched data). That's the case with the + // first and third indicators matched, here. + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + + matched: { + atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.port', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + provider: 'other_provider', + type: 'ip', + }, + matched: { + atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + + it('generates multiple signals with multiple matches', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + threat_language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', // narrow our query to a single record that matches two indicators + threat_indicator_path: 'threat.indicator', + threat_query: '*:*', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + { + entries: [ + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(2); + + const threats = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threats[0].enrichments, [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + type: 'url', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.port', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + + assertContains(threats[1].enrichments, [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + type: 'url', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + }); + + describe('indicator enrichment: event-first search', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/filebeat/threat_intel'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/threat_intel'); + }); + + it('enriches signals with the single indicator that matched', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: 'destination.ip:159.89.119.67', + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(2); + + const threats = previewAlerts.map((hit) => hit._source?.threat); + expect(threats).to.eql([ + { + enrichments: [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + type: 'url', + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ], + }, + { + enrichments: [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + type: 'url', + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ], + }, + ]); + }); + + it('enriches signals with multiple indicators if several matched', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: 'source.port: 57324', // narrow our query to a single record that matches two indicators + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(1); + + const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threat.enrichments, [ + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + provider: 'other_provider', + type: 'ip', + }, + + matched: { + atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + + it('adds a single indicator that matched multiple fields', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: 'source.port: 57324', // narrow our query to a single record that matches two indicators + threat_indicator_path: 'threat.indicator', + threat_query: 'threat.indicator.ip: *', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + ], + }, + { + entries: [ + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(1); + + const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threat.enrichments, [ + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + // We do not merge matched indicators during enrichment, so in + // certain circumstances a given indicator document could appear + // multiple times in an enriched alert (albeit with different + // threat.indicator.matched data). That's the case with the + // first and third indicators matched, here. + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + + matched: { + atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.port', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + provider: 'other_provider', + type: 'ip', + }, + matched: { + atomic: '45.115.45.3', + id: '978787', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + + it('generates multiple signals with multiple matches', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + threat_language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '(source.port:57324 and source.ip:45.115.45.3) or destination.ip:159.89.119.67', // narrow our query to a single record that matches two indicators + threat_indicator_path: 'threat.indicator', + threat_query: '*:*', + threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module + threat_mapping: [ + { + entries: [ + { + value: 'threat.indicator.port', + field: 'source.port', + type: 'mapping', + }, + { + value: 'threat.indicator.ip', + field: 'source.ip', + type: 'mapping', + }, + ], + }, + { + entries: [ + { + value: 'threat.indicator.domain', + field: 'destination.ip', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).equal(2); + + const threats = previewAlerts.map((hit) => hit._source?.threat) as Array<{ + enrichments: unknown[]; + }>; + + assertContains(threats[0].enrichments, [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + type: 'url', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: '45.115.45.3', + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + { + feed: {}, + indicator: { + description: 'this should match auditbeat/hosts on both port and ip', + first_seen: '2021-01-26T11:06:03.000Z', + ip: '45.115.45.3', + port: 57324, + provider: 'geenensp', + type: 'url', + }, + matched: { + atomic: 57324, + id: '978785', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'source.port', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + + assertContains(threats[1].enrichments, [ + { + feed: {}, + indicator: { + description: "domain should match the auditbeat hosts' data's source.ip", + domain: '159.89.119.67', + first_seen: '2021-01-26T11:09:04.000Z', + provider: 'geenensp', + type: 'url', + url: { + full: 'http://159.89.119.67:59600/bin.sh', + scheme: 'http', + }, + }, + matched: { + atomic: '159.89.119.67', + id: '978783', + index: 'filebeat-8.0.0-2021.01.26-000001', + field: 'destination.ip', + type: ENRICHMENT_TYPES.IndicatorMatchRule, + }, + }, + ]); + }); + }); + + describe('alerts should be enriched', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + it('should be enriched with host risk score', async () => { + const rule: ThreatMatchRuleCreateProps = { + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip + threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity + threat_mapping: [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId, size: 100 }); + expect(previewAlerts.length).equal(88); + const fullSource = previewAlerts.find( + (signal) => + (signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn' + ); + const fullSignal = fullSource?._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + + expect(fullSignal?.host?.risk?.calculated_level).to.eql('Critical'); + expect(fullSignal?.host?.risk?.calculated_score_norm).to.eql(70); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threshold.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threshold.ts new file mode 100644 index 0000000000000..e3294ae9a8156 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/threshold.ts @@ -0,0 +1,385 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { + ALERT_REASON, + ALERT_RULE_UUID, + ALERT_WORKFLOW_STATUS, + EVENT_KIND, +} from '@kbn/rule-data-utils'; + +import { ThresholdRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; +import { Ancestor } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/types'; +import { + ALERT_ANCESTORS, + ALERT_DEPTH, + ALERT_ORIGINAL_TIME, + ALERT_THRESHOLD_RESULT, +} from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { + createRule, + getOpenSignals, + getPreviewAlerts, + getThresholdRuleForSignalTesting, + previewRule, +} from '../../utils'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + describe('Threshold type rules', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'); + }); + + // First test creates a real rule - remaining tests use preview API + it('generates 1 signal from Threshold rules when threshold is met', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: ['host.id'], + value: 700, + }, + }; + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenSignals(supertest, log, es, createdRule); + expect(alerts.hits.hits.length).eql(1); + const fullSignal = alerts.hits.hits[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + const eventIds = (fullSignal?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); + expect(fullSignal).eql({ + ...fullSignal, + 'host.id': '8cc95778cce5407c809480e8e32ad76b', + [EVENT_KIND]: 'signal', + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: eventIds[0], + index: 'auditbeat-*', + type: 'event', + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_REASON]: 'event created high alert Signal Testing Query.', + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_DEPTH]: 1, + [ALERT_THRESHOLD_RESULT]: { + terms: [ + { + field: 'host.id', + value: '8cc95778cce5407c809480e8e32ad76b', + }, + ], + count: 788, + from: '2019-02-19T07:12:05.332Z', + }, + }); + }); + + it('generates 2 signals from Threshold rules when threshold is met', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: 'host.id', + value: 100, + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(2); + }); + + it('applies the provided query before bucketing ', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + query: 'host.id:"2ab45fc1c41e4c84bbd02202a7e5761f"', + threshold: { + field: 'process.name', + value: 21, + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + }); + + it('generates no signals from Threshold rules when threshold is met and cardinality is not met', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: 'host.id', + value: 100, + cardinality: [ + { + field: 'destination.ip', + value: 100, + }, + ], + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(0); + }); + + it('generates no signals from Threshold rules when cardinality is met and threshold is not met', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: 'host.id', + value: 1000, + cardinality: [ + { + field: 'destination.ip', + value: 5, + }, + ], + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(0); + }); + + it('generates signals from Threshold rules when threshold and cardinality are both met', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: 'host.id', + value: 100, + cardinality: [ + { + field: 'destination.ip', + value: 5, + }, + ], + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + const fullSignal = previewAlerts[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + const eventIds = (fullSignal?.[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); + expect(fullSignal).eql({ + ...fullSignal, + 'host.id': '8cc95778cce5407c809480e8e32ad76b', + [EVENT_KIND]: 'signal', + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: eventIds[0], + index: 'auditbeat-*', + type: 'event', + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_REASON]: `event created high alert Signal Testing Query.`, + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_DEPTH]: 1, + [ALERT_THRESHOLD_RESULT]: { + terms: [ + { + field: 'host.id', + value: '8cc95778cce5407c809480e8e32ad76b', + }, + ], + cardinality: [ + { + field: 'destination.ip', + value: 7, + }, + ], + count: 788, + from: '2019-02-19T07:12:05.332Z', + }, + }); + }); + + it('should not generate signals if only one field meets the threshold requirement', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: ['host.id', 'process.name'], + value: 22, + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(0); + }); + + it('generates signals from Threshold rules when bucketing by multiple fields', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: ['host.id', 'process.name', 'event.module'], + value: 21, + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(1); + const fullSignal = previewAlerts[0]._source; + if (!fullSignal) { + return expect(fullSignal).to.be.ok(); + } + const eventIds = (fullSignal[ALERT_ANCESTORS] as Ancestor[]).map((event) => event.id); + expect(fullSignal).eql({ + ...fullSignal, + 'event.module': 'system', + 'host.id': '2ab45fc1c41e4c84bbd02202a7e5761f', + 'process.name': 'sshd', + [EVENT_KIND]: 'signal', + [ALERT_ANCESTORS]: [ + { + depth: 0, + id: eventIds[0], + index: 'auditbeat-*', + type: 'event', + }, + ], + [ALERT_WORKFLOW_STATUS]: 'open', + [ALERT_REASON]: `event with process sshd, created high alert Signal Testing Query.`, + [ALERT_RULE_UUID]: fullSignal[ALERT_RULE_UUID], + [ALERT_ORIGINAL_TIME]: fullSignal[ALERT_ORIGINAL_TIME], + [ALERT_DEPTH]: 1, + [ALERT_THRESHOLD_RESULT]: { + terms: [ + { + field: 'host.id', + value: '2ab45fc1c41e4c84bbd02202a7e5761f', + }, + { + field: 'process.name', + value: 'sshd', + }, + { + field: 'event.module', + value: 'system', + }, + ], + count: 21, + from: '2019-02-19T20:22:03.561Z', + }, + }); + }); + + describe('Timestamp override and fallback', async () => { + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' + ); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/timestamp_fallback' + ); + }); + + it('applies timestamp override when using single field', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['timestamp-fallback-test']), + threshold: { + field: 'host.name', + value: 1, + }, + timestamp_override: 'event.ingested', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(4); + + for (const hit of previewAlerts) { + const originalTime = hit._source?.[ALERT_ORIGINAL_TIME]; + const hostName = hit._source?.['host.name']; + if (hostName === 'host-1') { + expect(originalTime).eql('2020-12-16T15:15:18.570Z'); + } else if (hostName === 'host-2') { + expect(originalTime).eql('2020-12-16T15:16:18.570Z'); + } else if (hostName === 'host-3') { + expect(originalTime).eql('2020-12-16T16:15:18.570Z'); + } else { + expect(originalTime).eql('2020-12-16T16:16:18.570Z'); + } + } + }); + + it('applies timestamp override when using multiple fields', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['timestamp-fallback-test']), + threshold: { + field: ['host.name', 'source.ip'], + value: 1, + }, + timestamp_override: 'event.ingested', + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).eql(4); + + for (const hit of previewAlerts) { + const originalTime = hit._source?.[ALERT_ORIGINAL_TIME]; + const hostName = hit._source?.['host.name']; + if (hostName === 'host-1') { + expect(originalTime).eql('2020-12-16T15:15:18.570Z'); + } else if (hostName === 'host-2') { + expect(originalTime).eql('2020-12-16T15:16:18.570Z'); + } else if (hostName === 'host-3') { + expect(originalTime).eql('2020-12-16T16:15:18.570Z'); + } else { + expect(originalTime).eql('2020-12-16T16:16:18.570Z'); + } + } + }); + }); + + describe('with host risk index', async () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/host_risk'); + }); + + it('should be enriched with host risk score', async () => { + const rule: ThresholdRuleCreateProps = { + ...getThresholdRuleForSignalTesting(['auditbeat-*']), + threshold: { + field: 'host.name', + value: 100, + }, + }; + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts[0]?._source?.host?.risk?.calculated_level).to.eql('Low'); + expect(previewAlerts[0]?._source?.host?.risk?.calculated_score_norm).to.eql(20); + expect(previewAlerts[1]?._source?.host?.risk?.calculated_level).to.eql('Critical'); + expect(previewAlerts[1]?._source?.host?.risk?.calculated_score_norm).to.eql(96); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/create_rule.ts b/x-pack/test/detection_engine_api_integration/utils/create_rule.ts index ab162724ecb68..278bf0a92b40f 100644 --- a/x-pack/test/detection_engine_api_integration/utils/create_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/create_rule.ts @@ -8,9 +8,9 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; import type { - CreateRulesSchema, - FullResponseSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + RuleCreateProps, + RuleResponse, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { deleteRule } from './delete_rule'; @@ -27,8 +27,8 @@ import { deleteRule } from './delete_rule'; export const createRule = async ( supertest: SuperTest.SuperTest<SuperTest.Test>, log: ToolingLog, - rule: CreateRulesSchema -): Promise<FullResponseSchema> => { + rule: RuleCreateProps +): Promise<RuleResponse> => { const response = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') diff --git a/x-pack/test/detection_engine_api_integration/utils/create_rule_with_auth.ts b/x-pack/test/detection_engine_api_integration/utils/create_rule_with_auth.ts index 266d01166d1b7..daa8ad420e4ca 100644 --- a/x-pack/test/detection_engine_api_integration/utils/create_rule_with_auth.ts +++ b/x-pack/test/detection_engine_api_integration/utils/create_rule_with_auth.ts @@ -9,9 +9,9 @@ import type SuperTest from 'supertest'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import type { - CreateRulesSchema, - FullResponseSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + RuleCreateProps, + RuleResponse, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * Helper to cut down on the noise in some of the tests. @@ -20,9 +20,9 @@ import type { */ export const createRuleWithAuth = async ( supertest: SuperTest.SuperTest<SuperTest.Test>, - rule: CreateRulesSchema, + rule: RuleCreateProps, auth: { user: string; pass: string } -): Promise<FullResponseSchema> => { +): Promise<RuleResponse> => { const { body } = await supertest .post(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') diff --git a/x-pack/test/detection_engine_api_integration/utils/create_rule_with_exception_entries.ts b/x-pack/test/detection_engine_api_integration/utils/create_rule_with_exception_entries.ts index 5b47de64d9c0f..d7f203eef82a4 100644 --- a/x-pack/test/detection_engine_api_integration/utils/create_rule_with_exception_entries.ts +++ b/x-pack/test/detection_engine_api_integration/utils/create_rule_with_exception_entries.ts @@ -9,9 +9,9 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; import type { NonEmptyEntriesArray, OsTypeArray } from '@kbn/securitysolution-io-ts-list-types'; import type { - CreateRulesSchema, - FullResponseSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + RuleCreateProps, + RuleResponse, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { createContainerWithEntries } from './create_container_with_entries'; @@ -31,13 +31,13 @@ import { createRule } from './create_rule'; export const createRuleWithExceptionEntries = async ( supertest: SuperTest.SuperTest<SuperTest.Test>, log: ToolingLog, - rule: CreateRulesSchema, + rule: RuleCreateProps, entries: NonEmptyEntriesArray[], endpointEntries?: Array<{ entries: NonEmptyEntriesArray; osTypes: OsTypeArray | undefined; }> -): Promise<FullResponseSchema> => { +): Promise<RuleResponse> => { const maybeExceptionList = await createContainerWithEntries(supertest, log, entries); const maybeEndpointList = await createContainerWithEndpointEntries( supertest, @@ -49,7 +49,7 @@ export const createRuleWithExceptionEntries = async ( // the rule to sometimes not filter correctly the first time with an exception list // or other timing issues. Then afterwards wait for the rule to have succeeded before // returning. - const ruleWithException: CreateRulesSchema = { + const ruleWithException: RuleCreateProps = { ...rule, enabled: false, exceptions_list: [...maybeExceptionList, ...maybeEndpointList], diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_exception_list.ts b/x-pack/test/detection_engine_api_integration/utils/delete_exception_list.ts index 499f5b94a9752..fdb6975aae5de 100644 --- a/x-pack/test/detection_engine_api_integration/utils/delete_exception_list.ts +++ b/x-pack/test/detection_engine_api_integration/utils/delete_exception_list.ts @@ -8,7 +8,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; import { EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants'; -import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * Helper to cut down on the noise in some of the tests. Does a delete of an exception list. @@ -21,7 +21,7 @@ export const deleteExceptionList = async ( supertest: SuperTest.SuperTest<SuperTest.Test>, log: ToolingLog, listId: string -): Promise<FullResponseSchema> => { +): Promise<RuleResponse> => { const response = await supertest .delete(`${EXCEPTION_LIST_URL}?list_id=${listId}`) .set('kbn-xsrf', 'true'); diff --git a/x-pack/test/detection_engine_api_integration/utils/delete_rule.ts b/x-pack/test/detection_engine_api_integration/utils/delete_rule.ts index 2e01c61c33595..a1678464def71 100644 --- a/x-pack/test/detection_engine_api_integration/utils/delete_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/delete_rule.ts @@ -7,7 +7,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; -import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; @@ -22,7 +22,7 @@ export const deleteRule = async ( supertest: SuperTest.SuperTest<SuperTest.Test>, log: ToolingLog, ruleId: string -): Promise<FullResponseSchema> => { +): Promise<RuleResponse> => { const response = await supertest .delete(`${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleId}`) .set('kbn-xsrf', 'true'); diff --git a/x-pack/test/detection_engine_api_integration/utils/find_immutable_rule_by_id.ts b/x-pack/test/detection_engine_api_integration/utils/find_immutable_rule_by_id.ts index 0e6fe73685c48..40d3557bba6d4 100644 --- a/x-pack/test/detection_engine_api_integration/utils/find_immutable_rule_by_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/find_immutable_rule_by_id.ts @@ -7,7 +7,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; -import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; @@ -24,7 +24,7 @@ export const findImmutableRuleById = async ( page: number; perPage: number; total: number; - data: FullResponseSchema[]; + data: RuleResponse[]; }> => { const response = await supertest .get( diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts index 4f5cfdcd3ba56..381f727f84585 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * This will return a complex rule with all the outputs possible * @param ruleId The ruleId to set which is optional and defaults to rule-1 */ -export const getComplexRule = (ruleId = 'rule-1'): CreateRulesSchema => ({ +export const getComplexRule = (ruleId = 'rule-1'): RuleCreateProps => ({ actions: [], author: [], name: 'Complex Rule Query', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts index 1491829b33999..a8f5916c3598d 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; // TODO: Follow up https://github.com/elastic/kibana/pull/137628 and add an explicit type to this object // without using Partial @@ -13,7 +13,7 @@ import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/de * This will return a complex rule with all the outputs possible * @param ruleId The ruleId to set which is optional and defaults to rule-1 */ -export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial<FullResponseSchema> => ({ +export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial<RuleResponse> => ({ actions: [], author: [], created_by: 'elastic', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_eql_rule_for_signal_testing.ts b/x-pack/test/detection_engine_api_integration/utils/get_eql_rule_for_signal_testing.ts index 21a8509a16460..4e9d48916ff68 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_eql_rule_for_signal_testing.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_eql_rule_for_signal_testing.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { EqlCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { EqlRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { getRuleForSignalTesting } from './get_rule_for_signal_testing'; /** @@ -19,7 +19,7 @@ export const getEqlRuleForSignalTesting = ( index: string[], ruleId = 'eql-rule', enabled = true -): EqlCreateSchema => ({ +): EqlRuleCreateProps => ({ ...getRuleForSignalTesting(index, ruleId, enabled), type: 'eql', language: 'eql', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notification_so.ts b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notification_so.ts index 9a084d800a2d8..836ad5390250e 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notification_so.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notification_so.ts @@ -6,9 +6,9 @@ */ import type { Client } from '@elastic/elasticsearch'; -import { SavedObjectReference } from '@kbn/core/server'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { LegacyRuleNotificationAlertTypeParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/notifications/legacy_types'; +import { SavedObjectReference } from '@kbn/core/server'; +import { LegacyRuleNotificationAlertTypeParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions_legacy'; interface LegacyActionNotificationSO extends LegacyRuleNotificationAlertTypeParams { references: SavedObjectReference[]; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notifications_so_by_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notifications_so_by_id.ts index 3120e85e899bf..2e104a454bf78 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notifications_so_by_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_notifications_so_by_id.ts @@ -6,9 +6,9 @@ */ import type { Client } from '@elastic/elasticsearch'; -import { SavedObjectReference } from '@kbn/core/server'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { LegacyRuleNotificationAlertTypeParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/notifications/legacy_types'; +import { SavedObjectReference } from '@kbn/core/server'; +import { LegacyRuleNotificationAlertTypeParams } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions_legacy'; interface LegacyActionNotificationSO extends LegacyRuleNotificationAlertTypeParams { references: SavedObjectReference[]; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_so.ts b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_so.ts index 2c714614f9caa..57cf8b5efe71a 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_so.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_legacy_action_so.ts @@ -7,7 +7,7 @@ import type { Client } from '@elastic/elasticsearch'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { SavedObjectReference } from '@kbn/core/server'; -import type { LegacyRuleActions } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions/legacy_types'; +import type { LegacyRuleActions } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions_legacy'; interface LegacyActionSO extends LegacyRuleActions { references: SavedObjectReference[]; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_legacy_actions_so_by_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_legacy_actions_so_by_id.ts index 48ea142aa28ca..1be0506359172 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_legacy_actions_so_by_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_legacy_actions_so_by_id.ts @@ -6,9 +6,9 @@ */ import type { Client } from '@elastic/elasticsearch'; -import { SavedObjectReference } from '@kbn/core/server'; import type { SearchResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { LegacyRuleActions } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions/legacy_types'; +import { SavedObjectReference } from '@kbn/core/server'; +import type { LegacyRuleActions } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_actions_legacy'; interface LegacyActionSO extends LegacyRuleActions { references: SavedObjectReference[]; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_open_signals.ts b/x-pack/test/detection_engine_api_integration/utils/get_open_signals.ts index f5d880cb3433e..ae370cb5886ea 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_open_signals.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_open_signals.ts @@ -9,7 +9,7 @@ import type SuperTest from 'supertest'; import type { Client } from '@elastic/elasticsearch'; import type { ToolingLog } from '@kbn/tooling-log'; import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring'; -import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { waitForRuleSuccessOrStatus } from './wait_for_rule_success_or_status'; import { refreshIndex } from './refresh_index'; @@ -19,7 +19,7 @@ export const getOpenSignals = async ( supertest: SuperTest.SuperTest<SuperTest.Test>, log: ToolingLog, es: Client, - rule: FullResponseSchema, + rule: RuleResponse, status: RuleExecutionStatus = RuleExecutionStatus.succeeded, size?: number ) => { diff --git a/x-pack/test/detection_engine_api_integration/utils/get_prepackaged_rule_status.ts b/x-pack/test/detection_engine_api_integration/utils/get_prepackaged_rule_status.ts index b6771cbb85f9c..df680cc12e9c8 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_prepackaged_rule_status.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_prepackaged_rule_status.ts @@ -7,9 +7,10 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; -import type { PrePackagedRulesAndTimelinesStatusSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/response'; - -import { DETECTION_ENGINE_PREPACKAGED_URL } from '@kbn/security-solution-plugin/common/constants'; +import { + PREBUILT_RULES_STATUS_URL, + GetPrebuiltRulesAndTimelinesStatusResponse, +} from '@kbn/security-solution-plugin/common/detection_engine/prebuilt_rules'; /** * Helper to cut down on the noise in some of the tests. This @@ -19,11 +20,8 @@ import { DETECTION_ENGINE_PREPACKAGED_URL } from '@kbn/security-solution-plugin/ export const getPrePackagedRulesStatus = async ( supertest: SuperTest.SuperTest<SuperTest.Test>, log: ToolingLog -): Promise<PrePackagedRulesAndTimelinesStatusSchema> => { - const response = await supertest - .get(`${DETECTION_ENGINE_PREPACKAGED_URL}/_status`) - .set('kbn-xsrf', 'true') - .send(); +): Promise<GetPrebuiltRulesAndTimelinesStatusResponse> => { + const response = await supertest.get(PREBUILT_RULES_STATUS_URL).set('kbn-xsrf', 'true').send(); if (response.status !== 200) { log.error( diff --git a/x-pack/test/detection_engine_api_integration/utils/get_preview_alerts.ts b/x-pack/test/detection_engine_api_integration/utils/get_preview_alerts.ts new file mode 100644 index 0000000000000..48682e6b1e8b0 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/get_preview_alerts.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Client } from '@elastic/elasticsearch'; +import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; +import { DetectionAlert } from '@kbn/security-solution-plugin/common/detection_engine/schemas/alerts'; +import { RiskEnrichmentFields } from '@kbn/security-solution-plugin/server/lib/detection_engine/signals/enrichments/types'; +import { refreshIndex } from './refresh_index'; + +/** + * Refresh an index, making changes available to search. + * Useful for tests where we want to ensure that a rule does NOT create alerts, e.g. testing exceptions. + * @param es The ElasticSearch handle + */ +export const getPreviewAlerts = async ({ + es, + previewId, + size, +}: { + es: Client; + previewId: string; + size?: number; +}) => { + const index = '.preview.alerts-security.alerts-*'; + await refreshIndex(es, index); + const query = { + bool: { + filter: { + term: { + [ALERT_RULE_UUID]: previewId, + }, + }, + }, + }; + const result = await es.search<DetectionAlert & RiskEnrichmentFields>({ + index, + size, + query, + }); + return result.hits.hits; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule.ts index b1036e1f8b682..0c9e77179709e 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_rule.ts @@ -7,7 +7,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; -import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; @@ -21,7 +21,7 @@ export const getRule = async ( supertest: SuperTest.SuperTest<SuperTest.Test>, log: ToolingLog, ruleId: string -): Promise<FullResponseSchema> => { +): Promise<RuleResponse> => { const response = await supertest .get(`${DETECTION_ENGINE_RULES_URL}?rule_id=${ruleId}`) .set('kbn-xsrf', 'true'); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing.ts index ee07daad625c7..321e821682878 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { QueryCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { QueryRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * This is a typical signal testing rule that is easy for most basic testing of output of signals. @@ -18,7 +18,7 @@ export const getRuleForSignalTesting = ( index: string[], ruleId = 'rule-1', enabled = true -): QueryCreateSchema => ({ +): QueryRuleCreateProps => ({ name: 'Signal Testing Query', description: 'Tests a simple query', enabled, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing_with_timestamp_override.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing_with_timestamp_override.ts index d742e727137c5..24ac2298ab68f 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing_with_timestamp_override.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_rule_for_signal_testing_with_timestamp_override.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { QueryCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { QueryRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; export const getRuleForSignalTestingWithTimestampOverride = ( index: string[], ruleId = 'rule-1', enabled = true, timestampOverride = 'event.ingested' -): QueryCreateSchema => ({ +): QueryRuleCreateProps => ({ name: 'Signal Testing Query', description: 'Tests a simple query', enabled, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_rule_with_web_hook_action.ts b/x-pack/test/detection_engine_api_integration/utils/get_rule_with_web_hook_action.ts index 02aaf938b0003..838ef235638e6 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_rule_with_web_hook_action.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_rule_with_web_hook_action.ts @@ -6,16 +6,16 @@ */ import type { - CreateRulesSchema, - UpdateRulesSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + RuleCreateProps, + RuleUpdateProps, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { getSimpleRule } from './get_simple_rule'; export const getRuleWithWebHookAction = ( id: string, enabled = false, - rule?: CreateRulesSchema -): CreateRulesSchema | UpdateRulesSchema => { + rule?: RuleCreateProps +): RuleCreateProps | RuleUpdateProps => { const finalRule = rule != null ? { ...rule, enabled } : getSimpleRule('rule-1', enabled); return { ...finalRule, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_saved_query_rule_for_signal_testing.ts b/x-pack/test/detection_engine_api_integration/utils/get_saved_query_rule_for_signal_testing.ts index da6c17d5a4026..12bca0207d4d6 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_saved_query_rule_for_signal_testing.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_saved_query_rule_for_signal_testing.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SavedQueryCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { SavedQueryRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { getRuleForSignalTesting } from './get_rule_for_signal_testing'; /** @@ -19,7 +19,7 @@ export const getSavedQueryRuleForSignalTesting = ( index: string[], ruleId = 'saved-query-rule', enabled = true -): SavedQueryCreateSchema => ({ +): SavedQueryRuleCreateProps => ({ ...getRuleForSignalTesting(index, ruleId, enabled), type: 'saved_query', saved_id: 'abcd', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule.ts index 5cf6c1c41aff4..7c70774847c87 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * This is a representative ML rule payload as expected by the server * @param ruleId The rule id * @param enabled Set to tru to enable it, by default it is off */ -export const getSimpleMlRule = (ruleId = 'rule-1', enabled = false): CreateRulesSchema => ({ +export const getSimpleMlRule = (ruleId = 'rule-1', enabled = false): RuleCreateProps => ({ name: 'Simple ML Rule', description: 'Simple Machine Learning Rule', enabled, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts index 56afa355b0482..754dbb1cd1149 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_output.ts @@ -5,11 +5,11 @@ * 2.0. */ -import type { MachineLearningResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { MachineLearningRule } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { getMockSharedResponseSchema } from './get_simple_rule_output'; import { removeServerGeneratedProperties } from './remove_server_generated_properties'; -const getBaseMlRuleOutput = (ruleId = 'rule-1'): MachineLearningResponseSchema => { +const getBaseMlRuleOutput = (ruleId = 'rule-1'): MachineLearningRule => { return { ...getMockSharedResponseSchema(ruleId), name: 'Simple ML Rule', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_update.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_update.ts index b5200ddd86357..219fb3e425255 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_update.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_ml_rule_update.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { UpdateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleUpdateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * This is a representative ML rule payload as expected by the server for an update * @param ruleId The rule id * @param enabled Set to tru to enable it, by default it is off */ -export const getSimpleMlRuleUpdate = (ruleId = 'rule-1', enabled = false): UpdateRulesSchema => ({ +export const getSimpleMlRuleUpdate = (ruleId = 'rule-1', enabled = false): RuleUpdateProps => ({ name: 'Simple ML Rule', description: 'Simple Machine Learning Rule', enabled, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_preview_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_preview_rule.ts index fa67ae3eeba80..985728e9bec80 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_preview_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_preview_rule.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { PreviewRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { PreviewRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * This is a typical simple preview rule for testing that is easy for most basic testing diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule.ts index a7e53a368b93c..e630ba9859e2f 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { QueryCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { QueryRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * This is a typical simple rule for testing that is easy for most basic testing * @param ruleId * @param enabled Enables the rule on creation or not. Defaulted to true. */ -export const getSimpleRule = (ruleId = 'rule-1', enabled = false): QueryCreateSchema => ({ +export const getSimpleRule = (ruleId = 'rule-1', enabled = false): QueryRuleCreateProps => ({ name: 'Simple Rule Query', description: 'Simple Rule Query', enabled, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts index 8d8cca2e71133..9e869a91bf0b1 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts @@ -6,15 +6,15 @@ */ import type { - FullResponseSchema, - SharedResponseSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + RuleResponse, + SharedResponseProps, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { removeServerGeneratedProperties } from './remove_server_generated_properties'; export const getMockSharedResponseSchema = ( ruleId = 'rule-1', enabled = false -): SharedResponseSchema => ({ +): SharedResponseProps => ({ actions: [], author: [], created_by: 'elastic', @@ -61,7 +61,7 @@ export const getMockSharedResponseSchema = ( namespace: undefined, }); -const getQueryRuleOutput = (ruleId = 'rule-1', enabled = false): FullResponseSchema => ({ +const getQueryRuleOutput = (ruleId = 'rule-1', enabled = false): RuleResponse => ({ ...getMockSharedResponseSchema(ruleId, enabled), index: ['auditbeat-*'], language: 'kuery', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_preview_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_preview_output.ts index 26fe0eafd1456..88f1efad4239e 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_preview_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_preview_output.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RulePreviewLogs } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RulePreviewLogs } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * This is the typical output of a simple rule preview, with errors and warnings coming up from the rule diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_update.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_update.ts index f4365386a8328..43da256b4e793 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_update.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_update.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { UpdateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleUpdateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * This is a typical simple rule for testing that is easy for most basic testing * @param ruleId The rule id * @param enabled Set to true to enable it, by default it is off */ -export const getSimpleRuleUpdate = (ruleId = 'rule-1', enabled = false): UpdateRulesSchema => ({ +export const getSimpleRuleUpdate = (ruleId = 'rule-1', enabled = false): RuleUpdateProps => ({ name: 'Simple Rule Query', description: 'Simple Rule Query', enabled, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_without_rule_id.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_without_rule_id.ts index 25994c8e6e14b..254cc044cbbe0 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_without_rule_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_without_rule_id.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { getSimpleRule } from './get_simple_rule'; /** * This is a typical simple rule for testing that is easy for most basic testing */ -export const getSimpleRuleWithoutRuleId = (): CreateRulesSchema => { +export const getSimpleRuleWithoutRuleId = (): RuleCreateProps => { const simpleRule = getSimpleRule(); // eslint-disable-next-line @typescript-eslint/naming-convention const { rule_id, ...ruleWithoutId } = simpleRule; diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_saved_query_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_saved_query_rule.ts index 2fdd157c9c0d4..56571463f85e3 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_saved_query_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_saved_query_rule.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SavedQueryCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { SavedQueryRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * This is a typical simple saved_query rule for e2e testing @@ -15,7 +15,7 @@ import type { SavedQueryCreateSchema } from '@kbn/security-solution-plugin/commo export const getSimpleSavedQueryRule = ( ruleId = 'rule-1', enabled = false -): SavedQueryCreateSchema => ({ +): SavedQueryRuleCreateProps => ({ name: 'Simple Saved Query Rule', description: 'Simple Saved Query Rule', enabled, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_threat_match.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_threat_match.ts index 57cca015d2c4b..d3013be402377 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_threat_match.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_threat_match.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ThreatMatchCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * This is a typical simple indicator match/threat match for testing that is easy for most basic testing @@ -15,7 +15,7 @@ import { ThreatMatchCreateSchema } from '@kbn/security-solution-plugin/common/de export const getSimpleThreatMatch = ( ruleId = 'rule-1', enabled = false -): ThreatMatchCreateSchema => ({ +): ThreatMatchRuleCreateProps => ({ description: 'Detecting root and admin users', name: 'Query with a rule id', severity: 'high', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_threat_match_rule_for_signal_testing.ts b/x-pack/test/detection_engine_api_integration/utils/get_threat_match_rule_for_signal_testing.ts index 3e663607c1186..24fa49c72cd09 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_threat_match_rule_for_signal_testing.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_threat_match_rule_for_signal_testing.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ThreatMatchCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { getRuleForSignalTesting } from './get_rule_for_signal_testing'; /** @@ -19,7 +19,7 @@ export const getThreatMatchRuleForSignalTesting = ( index: string[], ruleId = 'threat-match-rule', enabled = true -): ThreatMatchCreateSchema => ({ +): ThreatMatchRuleCreateProps => ({ ...getRuleForSignalTesting(index, ruleId, enabled), type: 'threat_match', language: 'kuery', diff --git a/x-pack/test/detection_engine_api_integration/utils/get_threshold_rule_for_signal_testing.ts b/x-pack/test/detection_engine_api_integration/utils/get_threshold_rule_for_signal_testing.ts index aea1a5746bf78..d37ec2084d5a0 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_threshold_rule_for_signal_testing.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_threshold_rule_for_signal_testing.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ThresholdCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { ThresholdRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { getRuleForSignalTesting } from './get_rule_for_signal_testing'; /** @@ -19,7 +19,7 @@ export const getThresholdRuleForSignalTesting = ( index: string[], ruleId = 'threshold-rule', enabled = true -): ThresholdCreateSchema => ({ +): ThresholdRuleCreateProps => ({ ...getRuleForSignalTesting(index, ruleId, enabled), type: 'threshold', language: 'kuery', diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts index 866136f172f12..b686589addc09 100644 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/index.ts @@ -41,6 +41,7 @@ export * from './get_legacy_action_so'; export * from './get_legacy_actions_so_by_id'; export * from './get_open_signals'; export * from './get_prepackaged_rule_status'; +export * from './get_preview_alerts'; export * from './get_query_all_signals'; export * from './get_query_signal_ids'; export * from './get_query_signals_ids'; @@ -79,6 +80,9 @@ export * from './get_slack_action'; export * from './get_web_hook_action'; export * from './index_event_log_execution_events'; export * from './install_prepackaged_rules'; +export * from './machine_learning_setup'; +export * from './preview_rule_with_exception_entries'; +export * from './preview_rule'; export * from './refresh_index'; export * from './remove_time_fields_from_telemetry_stats'; export * from './remove_server_generated_properties'; diff --git a/x-pack/test/detection_engine_api_integration/utils/install_prepackaged_rules.ts b/x-pack/test/detection_engine_api_integration/utils/install_prepackaged_rules.ts index c75e8203bf6cd..53fb592e84d13 100644 --- a/x-pack/test/detection_engine_api_integration/utils/install_prepackaged_rules.ts +++ b/x-pack/test/detection_engine_api_integration/utils/install_prepackaged_rules.ts @@ -8,7 +8,7 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; -import { DETECTION_ENGINE_PREPACKAGED_URL } from '@kbn/security-solution-plugin/common/constants'; +import { PREBUILT_RULES_URL } from '@kbn/security-solution-plugin/common/detection_engine/prebuilt_rules'; import { countDownTest } from './count_down_test'; export const installPrePackagedRules = async ( @@ -18,7 +18,7 @@ export const installPrePackagedRules = async ( await countDownTest( async () => { const { status, body } = await supertest - .put(DETECTION_ENGINE_PREPACKAGED_URL) + .put(PREBUILT_RULES_URL) .set('kbn-xsrf', 'true') .send(); if (status !== 200) { diff --git a/x-pack/test/detection_engine_api_integration/utils/machine_learning_setup.ts b/x-pack/test/detection_engine_api_integration/utils/machine_learning_setup.ts new file mode 100644 index 0000000000000..b376df9407c4b --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/machine_learning_setup.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 SuperTest from 'supertest'; + +export const executeSetupModuleRequest = async ({ + module, + rspCode, + supertest, +}: { + module: string; + rspCode: number; + supertest: SuperTest.SuperTest<SuperTest.Test>; +}) => { + const { body } = await supertest + .post(`/api/ml/modules/setup/${module}`) + .set('kbn-xsrf', 'true') + .send({ + prefix: '', + groups: ['auditbeat'], + indexPatternName: 'auditbeat-*', + startDatafeed: false, + useDedicatedIndex: true, + applyToAllSpaces: true, + }) + .expect(rspCode); + + return body; +}; + +export const forceStartDatafeeds = async ({ + jobId, + rspCode, + supertest, +}: { + jobId: string; + rspCode: number; + supertest: SuperTest.SuperTest<SuperTest.Test>; +}) => { + const { body } = await supertest + .post(`/api/ml/jobs/force_start_datafeeds`) + .set('kbn-xsrf', 'true') + .send({ + datafeedIds: [`datafeed-${jobId}`], + start: new Date().getUTCMilliseconds(), + }) + .expect(rspCode); + + return body; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/preview_rule.ts b/x-pack/test/detection_engine_api_integration/utils/preview_rule.ts new file mode 100644 index 0000000000000..1360209d9a175 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/preview_rule.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type SuperTest from 'supertest'; +import type { + RuleCreateProps, + PreviewRulesSchema, + RulePreviewLogs, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; + +import { DETECTION_ENGINE_RULES_PREVIEW } from '@kbn/security-solution-plugin/common/constants'; + +/** + * Runs the preview for a rule. Any generated alerts will be written to .preview.alerts. + * This is much faster than actually running the rule, and can also quickly simulate multiple + * consecutive rule runs, e.g. for ensuring that rule state is properly handled across runs. + * @param supertest The supertest deps + * @param rule The rule to create + */ +export const previewRule = async ({ + supertest, + rule, + invocationCount = 1, + timeframeEnd = new Date(), +}: { + supertest: SuperTest.SuperTest<SuperTest.Test>; + rule: RuleCreateProps; + invocationCount?: number; + timeframeEnd?: Date; +}): Promise<{ + previewId: string; + logs: RulePreviewLogs[]; + isAborted: boolean; +}> => { + const previewRequest: PreviewRulesSchema = { + ...rule, + invocationCount, + timeframeEnd: timeframeEnd.toISOString(), + }; + const response = await supertest + .post(DETECTION_ENGINE_RULES_PREVIEW) + .set('kbn-xsrf', 'true') + .send(previewRequest) + .expect(200); + return response.body; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/preview_rule_with_exception_entries.ts b/x-pack/test/detection_engine_api_integration/utils/preview_rule_with_exception_entries.ts new file mode 100644 index 0000000000000..efd5c71ac7047 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/preview_rule_with_exception_entries.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. + */ + +import type { ToolingLog } from '@kbn/tooling-log'; +import type SuperTest from 'supertest'; +import type { NonEmptyEntriesArray, OsTypeArray } from '@kbn/securitysolution-io-ts-list-types'; +import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; + +import { createContainerWithEntries } from './create_container_with_entries'; +import { createContainerWithEndpointEntries } from './create_container_with_endpoint_entries'; +import { previewRule } from './preview_rule'; + +/** + * Convenience testing function where you can pass in just the entries and you will + * get a rule created with the entries added to an exception list and exception list item + * all auto-created at once. + * @param supertest super test agent + * @param rule The rule to create and attach an exception list to + * @param entries The entries to create the rule and exception list from + * @param endpointEntries The endpoint entries to create the rule and exception list from + * @param osTypes The os types to optionally add or not to add to the container + */ +export const previewRuleWithExceptionEntries = async ({ + supertest, + log, + rule, + entries, + endpointEntries, + invocationCount, + timeframeEnd, +}: { + supertest: SuperTest.SuperTest<SuperTest.Test>; + log: ToolingLog; + rule: RuleCreateProps; + entries: NonEmptyEntriesArray[]; + endpointEntries?: Array<{ + entries: NonEmptyEntriesArray; + osTypes: OsTypeArray | undefined; + }>; + invocationCount?: number; + timeframeEnd?: Date; +}) => { + const maybeExceptionList = await createContainerWithEntries(supertest, log, entries); + const maybeEndpointList = await createContainerWithEndpointEntries( + supertest, + log, + endpointEntries ?? [] + ); + + return previewRule({ + supertest, + rule: { + ...rule, + exceptions_list: [...maybeExceptionList, ...maybeEndpointList], + }, + invocationCount, + timeframeEnd, + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts index 8d8a34bba8b79..b5c0bd1864ca8 100644 --- a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts +++ b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties.ts @@ -5,23 +5,20 @@ * 2.0. */ -import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { omit, pickBy } from 'lodash'; const serverGeneratedProperties = ['id', 'created_at', 'updated_at', 'execution_summary'] as const; type ServerGeneratedProperties = typeof serverGeneratedProperties[number]; -export type RuleWithoutServerGeneratedProperties = Omit< - FullResponseSchema, - ServerGeneratedProperties ->; +export type RuleWithoutServerGeneratedProperties = Omit<RuleResponse, ServerGeneratedProperties>; /** * This will remove server generated properties such as date times, etc... * @param rule Rule to pass in to remove typical server generated properties */ export const removeServerGeneratedProperties = ( - rule: FullResponseSchema + rule: RuleResponse ): RuleWithoutServerGeneratedProperties => { const removedProperties = omit(rule, serverGeneratedProperties); diff --git a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties_including_rule_id.ts b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties_including_rule_id.ts index e8e7e1900afb7..2a37c1b659093 100644 --- a/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties_including_rule_id.ts +++ b/x-pack/test/detection_engine_api_integration/utils/remove_server_generated_properties_including_rule_id.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { FullResponseSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { removeServerGeneratedProperties } from './remove_server_generated_properties'; @@ -14,8 +14,8 @@ import { removeServerGeneratedProperties } from './remove_server_generated_prope * @param rule Rule to pass in to remove typical server generated properties */ export const removeServerGeneratedPropertiesIncludingRuleId = ( - rule: FullResponseSchema -): Partial<FullResponseSchema> => { + rule: RuleResponse +): Partial<RuleResponse> => { const ruleWithRemovedProperties = removeServerGeneratedProperties(rule); // eslint-disable-next-line @typescript-eslint/naming-convention const { rule_id, ...additionalRuledIdRemoved } = ruleWithRemovedProperties; diff --git a/x-pack/test/detection_engine_api_integration/utils/rule_to_ndjson.ts b/x-pack/test/detection_engine_api_integration/utils/rule_to_ndjson.ts index 1082212432c01..03e6d266a3deb 100644 --- a/x-pack/test/detection_engine_api_integration/utils/rule_to_ndjson.ts +++ b/x-pack/test/detection_engine_api_integration/utils/rule_to_ndjson.ts @@ -5,14 +5,14 @@ * 2.0. */ -import type { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import type { RuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * Given a rule this will convert it to an ndjson buffer which is useful for * testing upload features. * @param rule The rule to convert to ndjson */ -export const ruleToNdjson = (rule: CreateRulesSchema): Buffer => { +export const ruleToNdjson = (rule: RuleCreateProps): Buffer => { const stringified = JSON.stringify(rule); return Buffer.from(`${stringified}\n`); }; diff --git a/x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts b/x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts index 32766c88978cd..7c10c98c105d6 100644 --- a/x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts +++ b/x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts @@ -6,9 +6,9 @@ */ import type { - FullResponseSchema, - UpdateRulesSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + RuleResponse, + RuleUpdateProps, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { omit, pickBy } from 'lodash'; const propertiesToRemove = [ @@ -25,12 +25,12 @@ const propertiesToRemove = [ ]; /** - * transforms FullResponseSchema rule to UpdateRulesSchema + * transforms RuleResponse rule to RuleUpdateProps * returned result can be used in rule update API calls */ -export const ruleToUpdateSchema = (rule: FullResponseSchema): UpdateRulesSchema => { +export const ruleToUpdateSchema = (rule: RuleResponse): RuleUpdateProps => { const removedProperties = omit(rule, propertiesToRemove); // We're only removing undefined values, so this cast correctly narrows the type - return pickBy(removedProperties, (value) => value !== undefined) as UpdateRulesSchema; + return pickBy(removedProperties, (value) => value !== undefined) as RuleUpdateProps; }; diff --git a/x-pack/test/detection_engine_api_integration/utils/update_rule.ts b/x-pack/test/detection_engine_api_integration/utils/update_rule.ts index c66d86c5594d0..cee09bc80a6c0 100644 --- a/x-pack/test/detection_engine_api_integration/utils/update_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/update_rule.ts @@ -10,9 +10,9 @@ import type SuperTest from 'supertest'; import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants'; import { - UpdateRulesSchema, - FullResponseSchema, -} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; + RuleUpdateProps, + RuleResponse, +} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; /** * Helper to cut down on the noise in some of the tests. This checks for @@ -23,8 +23,8 @@ import { export const updateRule = async ( supertest: SuperTest.SuperTest<SuperTest.Test>, log: ToolingLog, - updatedRule: UpdateRulesSchema -): Promise<FullResponseSchema> => { + updatedRule: RuleUpdateProps +): Promise<RuleResponse> => { const response = await supertest .put(DETECTION_ENGINE_RULES_URL) .set('kbn-xsrf', 'true') diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts index 277bafb998761..b8703cd5a1380 100644 --- a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts +++ b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts @@ -348,7 +348,7 @@ export default function (providerContext: FtrProviderContext) { return 0; } - const policyId = 'package-policy-test-1'; + const policyId = 'package-policy-test-'; packagePoliciesToDeleteIds.push(policyId); const getPkRes = await supertest .get(`/api/fleet/epm/packages/system`) @@ -438,6 +438,92 @@ export default function (providerContext: FtrProviderContext) { expect(await getSystemPackagePolicyCopyVersion(copy3Id)).to.be(3); }); + it('should work with package policy with space in name', async () => { + const policyId = 'package-policy-test-1'; + packagePoliciesToDeleteIds.push(policyId); + const getPkRes = await supertest + .get(`/api/fleet/epm/packages/system`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + systemPkgVersion = getPkRes.body.item.version; + // we must first force install the system package to override package verification error on policy create + const installPromise = supertest + .post(`/api/fleet/epm/packages/system-${systemPkgVersion}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }) + .expect(200); + + await Promise.all([ + installPromise, + kibanaServer.savedObjects.create({ + id: policyId, + type: PACKAGE_POLICY_SAVED_OBJECT_TYPE, + overwrite: true, + attributes: { + name: `system-1`, + package: { + name: 'system', + }, + }, + }), + ]); + + const { + body: { + item: { id: originalPolicyId }, + }, + } = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .query({ + sys_monitoring: false, + }) + .send({ + name: 'original policy with package policy with space in name', + namespace: 'default', + }) + .expect(200); + + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Filetest with space in name', + description: '', + namespace: 'default', + policy_id: originalPolicyId, + enabled: true, + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(200); + + const { + body: { + item: { id: copy1Id }, + }, + } = await supertest + .post(`/api/fleet/agent_policies/${originalPolicyId}/copy`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'copy 123', + description: 'Test', + }) + .expect(200); + + const { + body: { + item: { package_policies: packagePolicies }, + }, + } = await supertest.get(`/api/fleet/agent_policies/${copy1Id}`).expect(200); + + expect(packagePolicies[0].name).to.eql('Filetest with space in name (copy)'); + }); + it('should return a 404 with invalid source policy', async () => { await supertest .post(`/api/fleet/agent_policies/INVALID_POLICY_ID/copy`) diff --git a/x-pack/test/fleet_api_integration/apis/epm/index.js b/x-pack/test/fleet_api_integration/apis/epm/index.js index 137d7d59d8bfa..48af135f15ae2 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/index.js +++ b/x-pack/test/fleet_api_integration/apis/epm/index.js @@ -23,6 +23,7 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./install_remove_kbn_assets_in_space')); loadTestFile(require.resolve('./install_remove_multiple')); loadTestFile(require.resolve('./install_update')); + loadTestFile(require.resolve('./install_tag_assets')); loadTestFile(require.resolve('./bulk_upgrade')); loadTestFile(require.resolve('./update_assets')); loadTestFile(require.resolve('./data_stream')); diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_tag_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/install_tag_assets.ts new file mode 100644 index 0000000000000..7458912207a38 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/epm/install_tag_assets.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { setupFleetAndAgents } from '../agents/services'; +const testSpaceId = 'fleet_test_space'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const kibanaServer = getService('kibanaServer'); + const supertest = getService('supertest'); + const dockerServers = getService('dockerServers'); + const server = dockerServers.get('registry'); + const pkgName = 'only_dashboard'; + const pkgVersion = '0.1.0'; + + const uninstallPackage = async (pkg: string, version: string) => { + await supertest.delete(`/api/fleet/epm/packages/${pkg}/${version}`).set('kbn-xsrf', 'xxxx'); + }; + + const installPackageInSpace = async (pkg: string, version: string, spaceId: string) => { + await supertest + .post(`/s/${spaceId}/api/fleet/epm/packages/${pkg}/${version}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }) + .expect(200); + }; + const createSpace = async (spaceId: string) => { + await supertest + .post(`/api/spaces/space`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: spaceId, + id: spaceId, + initials: 's', + color: '#D6BF57', + disabledFeatures: [], + imageUrl: '', + }) + .expect(200); + }; + + const getTag = async (id: string, space?: string) => + kibanaServer.savedObjects + .get({ + type: 'tag', + id, + ...(space && { space }), + }) + .catch(() => {}); + + const deleteTag = async (id: string) => + kibanaServer.savedObjects + .delete({ + type: 'tag', + id, + }) + .catch(() => {}); + + const deleteSpace = async (spaceId: string) => { + await supertest.delete(`/api/spaces/space/${spaceId}`).set('kbn-xsrf', 'xxxx').send(); + }; + describe('asset tagging', () => { + skipIfNoDockerRegistry(providerContext); + setupFleetAndAgents(providerContext); + before(async () => { + await createSpace(testSpaceId); + }); + + after(async () => { + await deleteSpace(testSpaceId); + }); + describe('creates correct tags when installing a package in non default space after installing in default space', async () => { + before(async () => { + if (!server.enabled) return; + await installPackageInSpace('all_assets', pkgVersion, 'default'); + await installPackageInSpace(pkgName, pkgVersion, testSpaceId); + }); + after(async () => { + if (!server.enabled) return; + await uninstallPackage('all_assets', pkgVersion); + await uninstallPackage(pkgName, pkgVersion); + }); + + it('Should create managed tag saved objects', async () => { + const defaultTag = await getTag('fleet-managed-default'); + expect(defaultTag).not.equal(undefined); + + const spaceTag = await getTag('fleet-managed-fleet_test_space', testSpaceId); + expect(spaceTag).not.equal(undefined); + }); + it('Should create package tag saved objects', async () => { + const defaultTag = await getTag(`fleet-pkg-all_assets-default`); + expect(defaultTag).not.equal(undefined); + + const spaceTag = await getTag(`fleet-pkg-${pkgName}-fleet_test_space`, testSpaceId); + expect(spaceTag).not.equal(undefined); + }); + }); + + describe('Handles presence of legacy tags', async () => { + before(async () => { + if (!server.enabled) return; + + // first clean up any existing tag saved objects as they arent cleaned on uninstall + await deleteTag('fleet-managed-default'); + await deleteTag(`fleet-pkg-${pkgName}-default`); + + // now create the legacy tags + await kibanaServer.savedObjects.create({ + type: 'tag', + id: 'managed', + overwrite: false, + attributes: { + name: 'managed', + description: '', + color: '#FFFFFF', + }, + }); + await kibanaServer.savedObjects.create({ + type: 'tag', + id: pkgName, + overwrite: false, + attributes: { + name: pkgName, + description: '', + color: '#FFFFFF', + }, + }); + + await installPackageInSpace(pkgName, pkgVersion, 'default'); + }); + after(async () => { + if (!server.enabled) return; + await uninstallPackage(pkgName, pkgVersion); + await deleteTag('managed'); + await deleteTag('tag'); + }); + + it('Should not create space aware tag saved objects if legacy tags exist', async () => { + const managedTag = await getTag('fleet-managed-default'); + expect(managedTag).equal(undefined); + + const pkgTag = await getTag(`fleet-pkg-${pkgName}-default`); + expect(pkgTag).equal(undefined); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/group1/fields_list.ts b/x-pack/test/functional/apps/lens/group1/fields_list.ts new file mode 100644 index 0000000000000..3d571483bf9ac --- /dev/null +++ b/x-pack/test/functional/apps/lens/group1/fields_list.ts @@ -0,0 +1,233 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']); + const find = getService('find'); + const log = getService('log'); + const testSubjects = getService('testSubjects'); + const filterBar = getService('filterBar'); + const fieldEditor = getService('fieldEditor'); + const retry = getService('retry'); + + describe('lens fields list tests', () => { + for (const datasourceType of ['form-based', 'ad-hoc', 'ad-hoc-no-timefield']) { + describe(`${datasourceType} datasource`, () => { + before(async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + + if (datasourceType !== 'form-based') { + await PageObjects.lens.createAdHocDataView( + '*stash*', + datasourceType !== 'ad-hoc-no-timefield' + ); + retry.try(async () => { + const selectedPattern = await PageObjects.lens.getDataPanelIndexPattern(); + expect(selectedPattern).to.eql('*stash*'); + }); + } + + if (datasourceType !== 'ad-hoc-no-timefield') { + await PageObjects.lens.goToTimeRange(); + } + + await retry.try(async () => { + await PageObjects.lens.clickAddField(); + await fieldEditor.setName('runtime_string'); + await fieldEditor.enableValue(); + await fieldEditor.typeScript("emit('abc')"); + await fieldEditor.save(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + }); + + it('should show all fields as available', async () => { + expect( + await (await testSubjects.find('lnsIndexPatternAvailableFields-count')).getVisibleText() + ).to.eql(53); + }); + + it('should show a histogram and top values popover for numeric field', async () => { + const [fieldId] = await PageObjects.lens.findFieldIdsByType('number'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + // check for popover + await testSubjects.exists('lnsFieldListPanel-title'); + // check for top values chart + await testSubjects.existOrFail('lnsFieldListPanel-topValues'); + const topValuesRows = await testSubjects.findAll('lnsFieldListPanel-topValues-bucket'); + expect(topValuesRows.length).to.eql(11); + // check for the Other entry + expect(await topValuesRows[10].getVisibleText()).to.eql('Other\n96.7%'); + // switch to date histogram + await testSubjects.click('lnsFieldListPanel-buttonGroup-distributionButton'); + // check for date histogram chart + expect( + await find.existsByCssSelector( + '[data-test-subj="lnsFieldListPanelFieldContent"] .echChart' + ) + ).to.eql(true); + }); + + it('should show a top values popover for a keyword field', async () => { + const [fieldId] = await PageObjects.lens.findFieldIdsByType('string'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + // check for popover + await testSubjects.exists('lnsFieldListPanel-title'); + // check for top values chart + await testSubjects.existOrFail('lnsFieldListPanel-topValues'); + const topValuesRows = await testSubjects.findAll('lnsFieldListPanel-topValues-bucket'); + expect(topValuesRows.length).to.eql(11); + // check for the Other entry + expect(await topValuesRows[10].getVisibleText()).to.eql('Other\n99.9%'); + // check no date histogram + expect( + await find.existsByCssSelector( + '[data-test-subj="lnsFieldListPanelFieldContent"] .echChart' + ) + ).to.eql(false); + }); + + it('should show a date histogram popover for a date field', async () => { + const [fieldId] = await PageObjects.lens.findFieldIdsByType('date'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + // check for popover + await testSubjects.exists('lnsFieldListPanel-title'); + // check for date histogram chart + expect( + await find.existsByCssSelector( + '[data-test-subj="lnsFieldListPanelFieldContent"] .echChart' + ) + ).to.eql(true); + // check no top values chart + await testSubjects.missingOrFail('lnsFieldListPanel-buttonGroup-topValuesButton'); + }); + + it('should show a placeholder message about geo points field', async () => { + const [fieldId] = await PageObjects.lens.findFieldIdsByType('geo_point'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + const message = await testSubjects.getVisibleText('lnsFieldListPanel-missingFieldStats'); + expect(message).to.eql('Analysis is not available for this field.'); + }); + + it('should show stats for a numeric runtime field', async () => { + await PageObjects.lens.searchField('runtime'); + await PageObjects.lens.waitForField('runtime_number'); + const [fieldId] = await PageObjects.lens.findFieldIdsByType('number'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + // check for popover + await testSubjects.exists('lnsFieldListPanel-title'); + // check for top values chart + await testSubjects.existOrFail('lnsFieldListPanel-topValues'); + // check values + const topValuesRows = await testSubjects.findAll('lnsFieldListPanel-topValues-bucket'); + expect(topValuesRows.length).to.eql(11); + // check for the Other entry + expect(await topValuesRows[10].getVisibleText()).to.eql('Other\n96.7%'); + // switch to date histogram + await testSubjects.click('lnsFieldListPanel-buttonGroup-distributionButton'); + // check for date histogram chart + expect( + await find.existsByCssSelector( + '[data-test-subj="lnsFieldListPanelFieldContent"] .echChart' + ) + ).to.eql(true); + }); + + it('should show stats for a keyword runtime field', async () => { + await PageObjects.lens.searchField('runtime'); + await PageObjects.lens.waitForField('runtime_string'); + const [fieldId] = await PageObjects.lens.findFieldIdsByType('string'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + // check for popover + await testSubjects.exists('lnsFieldListPanel-title'); + // check for top values chart + await testSubjects.existOrFail('lnsFieldListPanel-topValues'); + // check no date histogram + expect( + await find.existsByCssSelector( + '[data-test-subj="lnsFieldListPanelFieldContent"] .echChart' + ) + ).to.eql(false); + await PageObjects.lens.searchField(''); + }); + + it('should change popover content if user defines a filter that affects field values', async () => { + // check the current records count for stats + const [fieldId] = await PageObjects.lens.findFieldIdsByType('string'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + const valuesCount = parseInt( + (await testSubjects.getVisibleText('lnsFieldListPanel-statsFooter')) + .replaceAll(/(Calculated from | records\.)/g, '') + .replace(',', ''), + 10 + ); + // define a filter + await filterBar.addFilter('geo.src', 'is', 'CN'); + await retry.waitFor('Wait for the filter to take effect', async () => { + await testSubjects.click(fieldId); + // check for top values chart has changed compared to the previous test + const newValuesCount = parseInt( + (await testSubjects.getVisibleText('lnsFieldListPanel-statsFooter')) + .replaceAll(/(Calculated from | records\.)/g, '') + .replace(',', ''), + 10 + ); + return newValuesCount < valuesCount; + }); + }); + + // One Fields cap's limitation is to not know when an index has no fields based on filters + it('should detect fields have no data in popup if filter excludes them', async () => { + await filterBar.removeAllFilters(); + await filterBar.addFilter('bytes', 'is', '-1'); + // check via popup fields have no data + const [fieldId] = await PageObjects.lens.findFieldIdsByType('string'); + await log.debug(`Opening field stats for ${fieldId}`); + await retry.try(async () => { + await testSubjects.click(fieldId); + expect(await testSubjects.find('lnsFieldListPanel-missingFieldStats')).to.be.ok(); + // close the popover + await testSubjects.click(fieldId); + }); + }); + + if (datasourceType !== 'ad-hoc-no-timefield') { + it('should move some fields as empty when the time range excludes them', async () => { + // remove the filter + await filterBar.removeAllFilters(); + // tweak the time range to 17 Sept 2015 to 18 Sept 2015 + await PageObjects.lens.goToTimeRange( + 'Sep 17, 2015 @ 06:31:44.000', + 'Sep 18, 2015 @ 06:31:44.000' + ); + // check all fields are empty now + expect( + await (await testSubjects.find('lnsIndexPatternEmptyFields-count')).getVisibleText() + ).to.eql(52); + // check avaialble count is 0 + expect( + await ( + await testSubjects.find('lnsIndexPatternAvailableFields-count') + ).getVisibleText() + ).to.eql(1); + }); + } + }); + } + }); +} diff --git a/x-pack/test/functional/apps/lens/group1/index.ts b/x-pack/test/functional/apps/lens/group1/index.ts index aa2b078a50a6b..302289319adbf 100644 --- a/x-pack/test/functional/apps/lens/group1/index.ts +++ b/x-pack/test/functional/apps/lens/group1/index.ts @@ -79,6 +79,8 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext loadTestFile(require.resolve('./table_dashboard')); loadTestFile(require.resolve('./table')); loadTestFile(require.resolve('./text_based_languages')); + loadTestFile(require.resolve('./fields_list')); + loadTestFile(require.resolve('./layer_actions')); } }); }; diff --git a/x-pack/test/functional/apps/lens/group1/layer_actions.ts b/x-pack/test/functional/apps/lens/group1/layer_actions.ts new file mode 100644 index 0000000000000..be22e4ad62511 --- /dev/null +++ b/x-pack/test/functional/apps/lens/group1/layer_actions.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']); + const find = getService('find'); + const testSubjects = getService('testSubjects'); + + describe('lens layer actions tests', () => { + it('should allow creation of lens xy chart', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.openLayerContextMenu(); + + // should be 3 actions available + expect( + (await find.allByCssSelector('[data-test-subj=lnsLayerActionsMenu] button')).length + ).to.eql(3); + }); + + it('should open layer settings for a data layer', async () => { + // click on open layer settings + await testSubjects.click('lnsLayerSettings'); + // random sampling available + await testSubjects.existOrFail('lns-indexPattern-random-sampling-row'); + // tweak the value + await PageObjects.lens.dragRangeInput('lns-indexPattern-random-sampling', 2, 'left'); + + expect(await PageObjects.lens.getRangeInputValue('lns-indexPattern-random-sampling')).to.eql( + 2 // 0.01 + ); + await testSubjects.click('lns-indexPattern-dimensionContainerBack'); + }); + + it('should add an annotation layer and settings shoud not be available', async () => { + // configure a date histogram + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + // add annotation layer + await testSubjects.click('lnsLayerAddButton'); + await testSubjects.click(`lnsLayerAddButton-annotations`); + await PageObjects.lens.openLayerContextMenu(1); + // layer settings not available + await testSubjects.missingOrFail('lnsLayerSettings'); + }); + + it('should switch to pie chart and have layer settings available', async () => { + await PageObjects.lens.switchToVisualization('pie'); + await PageObjects.lens.openLayerContextMenu(); + // layer settings still available + // open the panel + await testSubjects.click('lnsLayerSettings'); + // check the sampling value + expect(await PageObjects.lens.getRangeInputValue('lns-indexPattern-random-sampling')).to.eql( + 2 // 0.01 + ); + await testSubjects.click('lns-indexPattern-dimensionContainerBack'); + }); + + it('should switch to table and still have layer settings', async () => { + await PageObjects.lens.switchToVisualization('lnsDatatable'); + await PageObjects.lens.openLayerContextMenu(); + // layer settings still available + // open the panel + await testSubjects.click('lnsLayerSettings'); + // check the sampling value + expect(await PageObjects.lens.getRangeInputValue('lns-indexPattern-random-sampling')).to.eql( + 2 // 0.01 + ); + await testSubjects.click('lns-indexPattern-dimensionContainerBack'); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/heatmap.ts b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/heatmap.ts new file mode 100644 index 0000000000000..8556ae601daf9 --- /dev/null +++ b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/heatmap.ts @@ -0,0 +1,219 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, lens, visChart, timePicker, visEditor } = getPageObjects([ + 'visualize', + 'lens', + 'visChart', + 'timePicker', + 'visEditor', + ]); + + describe('Heatmap', function describeIndexTests() { + const isNewChartsLibraryEnabled = true; + + before(async () => { + await visualize.initTests(isNewChartsLibraryEnabled); + }); + + beforeEach(async () => { + await visualize.navigateToNewAggBasedVisualization(); + await visualize.clickHeatmapChart(); + await visualize.clickNewSearch(); + await timePicker.setDefaultAbsoluteRange(); + }); + + it('should show the "Edit Visualization in Lens" menu item if no X-axis was specified', async () => { + await visChart.waitForVisualizationRenderingStabilized(); + + expect(await visualize.hasNavigateToLensButton()).to.eql(true); + }); + + it('should show the "Edit Visualization in Lens" menu item', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + expect(await visualize.hasNavigateToLensButton()).to.eql(true); + }); + + it('should convert to Lens', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + // assert axes + expect(debugState.axes!.x[0].labels).to.eql(['win 8', 'win xp', 'win 7', 'ios', 'osx']); + expect(debugState.axes!.y[0].labels).to.eql(['']); + expect(debugState.heatmap!.cells.length).to.eql(5); + expect(debugState.legend!.items).to.eql([ + { + color: '#006837', + key: '0 - 25', + name: '0 - 25', + }, + { color: '#86CB66', key: '25 - 50', name: '25 - 50' }, + { + color: '#FEFEBD', + key: '50 - 75', + name: '50 - 75', + }, + { + color: '#F88D52', + key: '75 - 100', + name: '75 - 100', + }, + ]); + }); + + it('should convert to Lens if Y-axis is defined, but X-axis is not', async () => { + await visEditor.clickBucket('Y-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + expect(debugState.axes!.x[0].labels).to.eql(['*']); + expect(debugState.axes!.y[0].labels).to.eql(['win 8', 'win xp', 'win 7', 'ios', 'osx']); + expect(debugState.heatmap!.cells.length).to.eql(5); + }); + + it('should respect heatmap colors number', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visEditor.clickOptionsTab(); + await visEditor.changeHeatmapColorNumbers(6); + await visEditor.clickGo(); + await visChart.waitForVisualizationRenderingStabilized(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + expect(debugState.legend!.items).to.eql([ + { + color: '#006837', + key: '0 - 16.67', + name: '0 - 16.67', + }, + { + color: '#4CB15D', + key: '16.67 - 33.33', + name: '16.67 - 33.33', + }, + { + color: '#B7E075', + key: '33.33 - 50', + name: '33.33 - 50', + }, + { + color: '#FEFEBD', + key: '50 - 66.67', + name: '50 - 66.67', + }, + { + color: '#FDBF6F', + key: '66.67 - 83.33', + name: '66.67 - 83.33', + }, + { + color: '#EA5839', + key: '83.33 - 100', + name: '83.33 - 100', + }, + ]); + }); + + it('should show respect heatmap custom color ranges', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visEditor.clickOptionsTab(); + await visEditor.clickOptionsTab(); + await visEditor.clickEnableCustomRanges(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + + await visEditor.clickGo(); + await visChart.waitForVisualizationRenderingStabilized(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + expect(debugState.legend!.items).to.eql([ + { + color: '#006837', + key: '0 - 100', + name: '0 - 100', + }, + { + color: '#65BC62', + key: '100 - 200', + name: '100 - 200', + }, + { + color: '#D8EF8C', + key: '200 - 300', + name: '200 - 300', + }, + { + color: '#FEDF8B', + key: '300 - 400', + name: '300 - 400', + }, + { + color: '#F36D43', + key: '400 - 500', + name: '400 - 500', + }, + { + color: '#A50026', + key: '500 - 600', + name: '500 - 600', + }, + ]); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts index 87c9d025893a1..52ef856d53ef6 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts @@ -15,5 +15,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./gauge')); loadTestFile(require.resolve('./goal')); loadTestFile(require.resolve('./table')); + loadTestFile(require.resolve('./heatmap')); }); } diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/index.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/index.ts index c0b5197983aa4..8428d145c60ef 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/index.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./timeseries')); loadTestFile(require.resolve('./dashboard')); loadTestFile(require.resolve('./top_n')); + loadTestFile(require.resolve('./table')); }); } diff --git a/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts new file mode 100644 index 0000000000000..e3d52852cd61b --- /dev/null +++ b/x-pack/test/functional/apps/lens/open_in_lens/tsvb/table.ts @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, visualBuilder, lens, header } = getPageObjects([ + 'visualBuilder', + 'visualize', + 'header', + 'lens', + ]); + + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + + describe('Table', function describeIndexTests() { + before(async () => { + await visualize.initTests(); + }); + + beforeEach(async () => { + await visualBuilder.resetPage(); + await visualBuilder.clickTable(); + await header.waitUntilLoadingHasFinished(); + await visualBuilder.checkTableTabIsPresent(); + await visualBuilder.selectGroupByField('machine.os.raw'); + }); + + it('should not allow converting of not valid panel', async () => { + await visualBuilder.selectAggType('Max'); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting of unsupported aggregations', async () => { + await visualBuilder.selectAggType('Sum of Squares'); + await visualBuilder.setFieldForAggregation('machine.ram'); + + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting sibling pipeline aggregations', async () => { + await visualBuilder.createNewAgg(); + + await visualBuilder.selectAggType('Overall Average', 1); + await visualBuilder.setFieldForAggregation('Count', 1); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting parent pipeline aggregations', async () => { + await visualBuilder.clickPanelOptions('table'); + await visualBuilder.setMetricsDataTimerangeMode('Last value'); + await visualBuilder.clickDataTab('table'); + await visualBuilder.createNewAgg(); + + await visualBuilder.selectAggType('Cumulative Sum', 1); + await visualBuilder.setFieldForAggregation('Count', 1); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting not valid aggregation function', async () => { + await visualBuilder.clickSeriesOption(); + await visualBuilder.setFieldForAggregateBy('clientip'); + await visualBuilder.setFunctionForAggregateFunction('Cumulative Sum'); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should not allow converting series with different aggregation fucntion or aggregation by', async () => { + await visualBuilder.createNewAggSeries(); + await visualBuilder.selectAggType('Static Value', 1); + await visualBuilder.setStaticValue(10); + await visualBuilder.clickSeriesOption(); + await visualBuilder.setFieldForAggregateBy('bytes'); + await visualBuilder.setFunctionForAggregateFunction('Sum'); + await visualBuilder.clickSeriesOption(1); + await visualBuilder.setFieldForAggregateBy('bytes'); + await visualBuilder.setFunctionForAggregateFunction('Min'); + await header.waitUntilLoadingHasFinished(); + expect(await visualize.hasNavigateToLensButton()).to.be(false); + }); + + it('should allow converting a count aggregation', async () => { + expect(await visualize.hasNavigateToLensButton()).to.be(true); + }); + + it('should convert last value mode to reduced time range', async () => { + await visualBuilder.clickPanelOptions('table'); + await visualBuilder.setMetricsDataTimerangeMode('Last value'); + await visualBuilder.setIntervalValue('1m'); + await visualBuilder.clickDataTab('table'); + await header.waitUntilLoadingHasFinished(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('lnsDataTable'); + await lens.openDimensionEditor('lnsDatatable_metrics > lns-dimensionTrigger'); + await testSubjects.click('indexPattern-advanced-accordion'); + const reducedTimeRange = await testSubjects.find('indexPattern-dimension-reducedTimeRange'); + expect(await reducedTimeRange.getVisibleText()).to.be('1 minute (1m)'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(1); + const metricDimensionText = await lens.getDimensionTriggerText('lnsDatatable_metrics', 0); + expect(metricDimensionText).to.be('Count of records last 1m'); + }); + }); + + it('should convert static value to the metric dimension', async () => { + await visualBuilder.createNewAggSeries(); + await visualBuilder.selectAggType('Static Value', 1); + await visualBuilder.setStaticValue(10); + + await header.waitUntilLoadingHasFinished(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('lnsDataTable'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(1); + const metricDimensionText1 = await lens.getDimensionTriggerText('lnsDatatable_metrics', 0); + const metricDimensionText2 = await lens.getDimensionTriggerText('lnsDatatable_metrics', 1); + expect(metricDimensionText1).to.be('Count of records'); + expect(metricDimensionText2).to.be('10'); + }); + }); + + it('should convert aggregate by to split row dimension', async () => { + await visualBuilder.clickSeriesOption(); + await visualBuilder.setFieldForAggregateBy('clientip'); + await visualBuilder.setFunctionForAggregateFunction('Sum'); + await header.waitUntilLoadingHasFinished(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('lnsDataTable'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(1); + const splitRowsText1 = await lens.getDimensionTriggerText('lnsDatatable_rows', 0); + const splitRowsText2 = await lens.getDimensionTriggerText('lnsDatatable_rows', 1); + expect(splitRowsText1).to.be('Top 10 values of machine.os.raw'); + expect(splitRowsText2).to.be('Top 10 values of clientip'); + }); + + await lens.openDimensionEditor('lnsDatatable_rows > lns-dimensionTrigger', 0, 1); + const collapseBy = await testSubjects.find('indexPattern-collapse-by'); + expect(await collapseBy.getAttribute('value')).to.be('sum'); + }); + + it('should convert group by field with custom label', async () => { + await visualBuilder.setColumnLabelValue('test'); + await header.waitUntilLoadingHasFinished(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('lnsDataTable'); + await retry.try(async () => { + const layerCount = await lens.getLayerCount(); + expect(layerCount).to.be(1); + const splitRowsText = await lens.getDimensionTriggerText('lnsDatatable_rows', 0); + expect(splitRowsText).to.be('test'); + }); + }); + + it('should convert color ranges', async () => { + await visualBuilder.clickSeriesOption(); + + await visualBuilder.setColorRuleOperator('>= greater than or equal'); + await visualBuilder.setColorRuleValue(10); + await visualBuilder.setColorPickerValue('#54B399'); + + await visualBuilder.createColorRule(1); + + await visualBuilder.setColorRuleOperator('>= greater than or equal'); + await visualBuilder.setColorRuleValue(100, 1); + await visualBuilder.setColorPickerValue('#54A000', 1); + + await header.waitUntilLoadingHasFinished(); + await visualize.navigateToLensFromAnotherVisulization(); + + await lens.waitForVisualization('lnsDataTable'); + await retry.try(async () => { + const closePalettePanels = await testSubjects.findAll( + 'lns-indexPattern-PalettePanelContainerBack' + ); + if (closePalettePanels.length) { + await lens.closePalettePanel(); + await lens.closeDimensionEditor(); + } + + await lens.openDimensionEditor('lnsDatatable_metrics > lns-dimensionTrigger'); + + await lens.openPalettePanel('lnsDatatable'); + const colorStops = await lens.getPaletteColorStops(); + + expect(colorStops).to.eql([ + { stop: '10', color: 'rgba(84, 179, 153, 1)' }, + { stop: '100', color: 'rgba(84, 160, 0, 1)' }, + { stop: '', color: undefined }, + ]); + }); + }); + }); +} diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index e92ec8be1916c..11ba4d6ebd447 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -52,7 +52,6 @@ export default async function ({ readConfigFile }) { '--xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled=true', '--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects, '--uiSettings.overrides.observability:enableNewSyntheticsView=true', // for OSS test management/_import_objects, - '--guidedOnboarding.ui=true', // Enable guided onboarding for infra/tour.ts tests ], }, uiSettings: { diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/data.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/data.json.gz index 26952621f10e4..6a7559434f703 100644 Binary files a/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/data.json.gz and b/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/mappings.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/mappings.json.gz index 3a26e140e7eaa..5a7347bcb63de 100644 Binary files a/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/mappings.json.gz and b/x-pack/test/functional/es_archives/security_solution/alerts/8.0.0/mappings.json.gz differ diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/data.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/data.json.gz index 1bd5cf631c289..50d664aa24ced 100644 Binary files a/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/data.json.gz and b/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/mappings.json.gz b/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/mappings.json.gz index 05640f1e93e34..27ba06da3ad3d 100644 Binary files a/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/mappings.json.gz and b/x-pack/test/functional/es_archives/security_solution/alerts/8.1.0/mappings.json.gz differ diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index a336a0da3e4ba..c814b5b161fcd 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -600,6 +600,19 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await this.waitForVisualization(); }, + async dragRangeInput(testId: string, steps: number = 1, direction: 'left' | 'right' = 'right') { + const inputEl = await testSubjects.find(testId); + await inputEl.focus(); + const browserKey = direction === 'left' ? browser.keys.LEFT : browser.keys.RIGHT; + while (steps--) { + await browser.pressKeys(browserKey); + } + }, + + async getRangeInputValue(testId: string) { + return (await testSubjects.find(testId)).getAttribute('value'); + }, + async isTopLevelAggregation() { return await testSubjects.isEuiSwitchChecked('indexPattern-nesting-switch'); }, @@ -1117,6 +1130,10 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont ); }, + async openLayerContextMenu(index: number = 0) { + await testSubjects.click(`lnsLayerSplitButton--${index}`); + }, + async toggleColumnVisibility(dimension: string, no = 1) { await this.openDimensionEditor(dimension); const id = 'lns-table-column-hidden'; @@ -1318,9 +1335,9 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await testSubjects.click('indexPattern-add-field'); }, - async createAdHocDataView(name: string) { + async createAdHocDataView(name: string, hasTimeField?: boolean) { await testSubjects.click('lns-dataView-switch-link'); - await PageObjects.unifiedSearch.createNewDataView(name, true); + await PageObjects.unifiedSearch.createNewDataView(name, true, hasTimeField); }, async switchToTextBasedLanguage(language: string) { @@ -1621,5 +1638,17 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }) ); }, + + async findFieldIdsByType( + type: 'string' | 'number' | 'date' | 'geo_point' | 'ip_range', + group: 'available' | 'empty' | 'meta' = 'available' + ) { + const groupCapitalized = `${group[0].toUpperCase()}${group.slice(1).toLowerCase()}`; + const allFieldsForType = await find.allByCssSelector( + `[data-test-subj="lnsIndexPattern${groupCapitalized}Fields"] .lnsFieldItem--${type}` + ); + // map to testSubjId + return Promise.all(allFieldsForType.map((el) => el.getAttribute('data-test-subj'))); + }, }); } diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts index 9eb23f62e06fe..c4b5a2831183c 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts @@ -54,7 +54,8 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const testHistoryIndex = '.kibana_task_manager_test_result'; - describe('scheduling and running tasks', () => { + // FLAKY: https://github.com/elastic/kibana/issues/141055 + describe.skip('scheduling and running tasks', () => { beforeEach(async () => { // clean up before each test return await supertest.delete('/api/sample_tasks').set('kbn-xsrf', 'xxx').expect(200); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/validation.ts b/x-pack/test/reporting_api_integration/reporting_and_security/validation.ts index dfd75d2e65248..aafd24b7a08e2 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/validation.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/validation.ts @@ -27,7 +27,8 @@ export default function ({ getService }: FtrProviderContext) { } }; - describe('Job parameter validation', () => { + // Failing: See https://github.com/elastic/kibana/issues/143717 + describe.skip('Job parameter validation', () => { before(async () => { await reportingAPI.initEcommerce(); }); diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts index 11982a8b51425..b5c72abac6329 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { RuleRegistrySearchResponse } from '@kbn/rule-registry-plugin/common/search_strategy'; -import { QueryCreateSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { QueryRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { deleteSignalsIndex, @@ -19,7 +19,6 @@ import { waitForSignalsToBePresent, waitForRuleSuccessOrStatus, } from '../../../../detection_engine_api_integration/utils'; -import { ID } from '../../../../detection_engine_api_integration/security_and_spaces/group1/generating_signals'; import { obsOnlySpacesAllEsRead, obsOnlySpacesAll, @@ -31,6 +30,8 @@ type RuleRegistrySearchResponseWithErrors = RuleRegistrySearchResponse & { message: string; }; +const ID = 'BhbXBmkBR346wHgn4PeZ'; + // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); @@ -118,7 +119,7 @@ export default ({ getService }: FtrProviderContext) => { await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'); await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); - const rule: QueryCreateSchema = { + const rule: QueryRuleCreateProps = { ...getRuleForSignalTesting(['auditbeat-*']), query: `_id:${ID}`, }; diff --git a/x-pack/test/security_solution_ftr/services/detections/index.ts b/x-pack/test/security_solution_ftr/services/detections/index.ts index 1d3e18f1ab43d..024dede892be5 100644 --- a/x-pack/test/security_solution_ftr/services/detections/index.ts +++ b/x-pack/test/security_solution_ftr/services/detections/index.ts @@ -13,8 +13,8 @@ import { DETECTION_ENGINE_RULES_URL, } from '@kbn/security-solution-plugin/common/constants'; import { estypes } from '@elastic/elasticsearch'; -import endpointPrePackagedRule from '@kbn/security-solution-plugin/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json'; -import { Rule } from '@kbn/security-solution-plugin/public/detections/containers/detection_engine/rules'; +import endpointPrePackagedRule from '@kbn/security-solution-plugin/server/lib/detection_engine/prebuilt_rules/content/prepackaged_rules/elastic_endpoint_security.json'; +import { Rule } from '@kbn/security-solution-plugin/public/detection_engine/rule_management/logic/types'; import { FtrService } from '../../../functional/ftr_provider_context'; export class DetectionsTestService extends FtrService { diff --git a/x-pack/test/stack_functional_integration/apps/filebeat/filebeat.ts b/x-pack/test/stack_functional_integration/apps/filebeat/filebeat.ts index c8ddb8aabec30..5539125d770a8 100644 --- a/x-pack/test/stack_functional_integration/apps/filebeat/filebeat.ts +++ b/x-pack/test/stack_functional_integration/apps/filebeat/filebeat.ts @@ -18,7 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('filebeat-*'); await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await retry.try(async () => { - const hitCount = parseInt(await PageObjects.discover.getHitCount(), 10); + const hitCount = await PageObjects.discover.getHitCountInt(); expect(hitCount).to.be.greaterThan(0); }); }); diff --git a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat.ts b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat.ts index f6a8aa7875302..50b254a65559e 100644 --- a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat.ts +++ b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat.ts @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('metricbeat-*'); await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await retry.try(async function () { - const hitCount = parseInt(await PageObjects.discover.getHitCount(), 10); + const hitCount = await PageObjects.discover.getHitCountInt(); expect(hitCount).to.be.greaterThan(0); }); }); diff --git a/x-pack/test/stack_functional_integration/apps/packetbeat/_packetbeat.ts b/x-pack/test/stack_functional_integration/apps/packetbeat/_packetbeat.ts index e4bf8288a2093..367f7a34f7003 100644 --- a/x-pack/test/stack_functional_integration/apps/packetbeat/_packetbeat.ts +++ b/x-pack/test/stack_functional_integration/apps/packetbeat/_packetbeat.ts @@ -32,7 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('packetbeat-*'); await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await retry.try(async function () { - const hitCount = parseInt(await PageObjects.discover.getHitCount(), 10); + const hitCount = await PageObjects.discover.getHitCountInt(); expect(hitCount).to.be.greaterThan(0); }); }); diff --git a/x-pack/test/stack_functional_integration/apps/winlogbeat/_winlogbeat.ts b/x-pack/test/stack_functional_integration/apps/winlogbeat/_winlogbeat.ts index 4f8107f937a77..99bcf4e15dcd6 100644 --- a/x-pack/test/stack_functional_integration/apps/winlogbeat/_winlogbeat.ts +++ b/x-pack/test/stack_functional_integration/apps/winlogbeat/_winlogbeat.ts @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('winlogbeat-*'); await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await retry.try(async function () { - const hitCount = parseInt(await PageObjects.discover.getHitCount(), 10); + const hitCount = await PageObjects.discover.getHitCountInt(); expect(hitCount).to.be.greaterThan(0); }); }); diff --git a/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts b/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts index bfb51f3e5336d..64adb488ca2b9 100644 --- a/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/discover/discover_smoke_tests.ts @@ -40,12 +40,12 @@ export default function ({ getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern(String(index)); await PageObjects.discover.waitUntilSearchingHasFinished(); if (timefield) { - await PageObjects.timePicker.setCommonlyUsedTime('Last_24 hours'); + await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await PageObjects.discover.waitUntilSearchingHasFinished(); } }); it('shows hit count greater than zero', async () => { - const hitCount = await PageObjects.discover.getHitCount(); + const hitCount = await PageObjects.discover.getHitCountInt(); if (hits === '') { expect(hitCount).to.be.greaterThan(0); } else { @@ -69,12 +69,12 @@ export default function ({ getPageObjects }: FtrProviderContext) { await PageObjects.home.launchSampleDiscover(name); await PageObjects.header.waitUntilLoadingHasFinished(); if (timefield) { - await PageObjects.timePicker.setCommonlyUsedTime('Last_24 hours'); + await PageObjects.timePicker.setCommonlyUsedTime('Last_1 year'); await PageObjects.discover.waitUntilSearchingHasFinished(); } }); it('shows hit count greater than zero', async () => { - const hitCount = await PageObjects.discover.getHitCount(); + const hitCount = await PageObjects.discover.getHitCountInt(); if (hits === '') { expect(hitCount).to.be.greaterThan(0); } else { diff --git a/yarn.lock b/yarn.lock index 39da46195d79a..9ed18bf2180b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd" + integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g== + "@ampproject/remapping@^2.1.0": version "2.1.2" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" @@ -81,10 +86,10 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.3.tgz#707b939793f867f5a73b2666e6d9a3396eb03151" - integrity sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.19.3", "@babel/compat-data@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.4.tgz#95c86de137bf0317f3a570e1b6e996b427299747" + integrity sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw== "@babel/core@7.12.9": version "7.12.9" @@ -108,21 +113,21 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.19.3", "@babel/core@^7.7.5": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.3.tgz#2519f62a51458f43b682d61583c3810e7dcee64c" - integrity sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ== +"@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.19.6", "@babel/core@^7.7.2", "@babel/core@^7.7.5", "@babel/core@^7.8.0": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.6.tgz#7122ae4f5c5a37c0946c066149abd8e75f81540f" + integrity sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.3" + "@babel/generator" "^7.19.6" "@babel/helper-compilation-targets" "^7.19.3" - "@babel/helper-module-transforms" "^7.19.0" - "@babel/helpers" "^7.19.0" - "@babel/parser" "^7.19.3" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helpers" "^7.19.4" + "@babel/parser" "^7.19.6" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.3" - "@babel/types" "^7.19.3" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -145,12 +150,12 @@ dependencies: eslint-rule-composer "^0.3.0" -"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.3.tgz#d7f4d1300485b4547cb6f94b27d10d237b42bf59" - integrity sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ== +"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.19.6", "@babel/generator@^7.7.2": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.6.tgz#9e481a3fe9ca6261c972645ae3904ec0f9b34a1d" + integrity sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA== dependencies: - "@babel/types" "^7.19.3" + "@babel/types" "^7.19.4" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -267,19 +272,19 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" - integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0", "@babel/helper-module-transforms@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz#6c52cc3ac63b70952d33ee987cbee1c9368b533f" + integrity sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw== dependencies: "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-simple-access" "^7.19.4" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -319,12 +324,12 @@ "@babel/traverse" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-simple-access@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" - integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== +"@babel/helper-simple-access@^7.18.6", "@babel/helper-simple-access@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz#be553f4951ac6352df2567f7daa19a0ee15668e7" + integrity sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.19.4" "@babel/helper-skip-transparent-expression-wrappers@^7.18.9": version "7.18.9" @@ -340,10 +345,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" - integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" @@ -365,14 +370,14 @@ "@babel/traverse" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" - integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.4.tgz#42154945f87b8148df7203a25c31ba9a73be46c5" + integrity sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw== dependencies: "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" + "@babel/traverse" "^7.19.4" + "@babel/types" "^7.19.4" "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" @@ -383,10 +388,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.3.tgz#8dd36d17c53ff347f9e55c328710321b49479a9a" - integrity sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.6.tgz#b923430cb94f58a7eae8facbffa9efd19130e7f8" + integrity sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -505,14 +510,14 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-transform-parameters" "^7.12.1" -"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7" - integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== +"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz#a8fc86e8180ff57290c91a75d83fe658189b642d" + integrity sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q== dependencies: - "@babel/compat-data" "^7.18.8" - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/compat-data" "^7.19.4" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.18.8" @@ -713,7 +718,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.18.6": +"@babel/plugin-syntax-typescript@^7.18.6", "@babel/plugin-syntax-typescript@^7.7.2": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz#1c09cd25795c7c2b8a4ba9ae49394576d4133285" integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== @@ -743,12 +748,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoping@^7.12.12", "@babel/plugin-transform-block-scoping@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz#f9b7e018ac3f373c81452d6ada8bd5a18928926d" - integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw== +"@babel/plugin-transform-block-scoping@^7.12.12", "@babel/plugin-transform-block-scoping@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.19.4.tgz#315d70f68ce64426db379a3d830e7ac30be02e9b" + integrity sha512-934S2VLLlt2hRJwPf4MczaOr4hYF0z+VKPwqTNxyKX7NthTiPfhuKFWQZHXRM0vh/wo/VyXB3s4bZUNA08l+tQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-classes@^7.12.1", "@babel/plugin-transform-classes@^7.19.0": version "7.19.0" @@ -772,12 +777,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-destructuring@^7.12.1", "@babel/plugin-transform-destructuring@^7.18.13": - version "7.18.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz#9e03bc4a94475d62b7f4114938e6c5c33372cbf5" - integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== +"@babel/plugin-transform-destructuring@^7.12.1", "@babel/plugin-transform-destructuring@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.19.4.tgz#46890722687b9b89e1369ad0bd8dc6c5a3b4319d" + integrity sha512-t0j0Hgidqf0aM86dF8U+vXYReUgJnlv4bZLsyoPnwZNrGY+7/38o8YjaELrvHeVfTZao15kjR0PVv0nju2iduA== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.18.6" @@ -963,10 +968,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-runtime@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz#a3df2d7312eea624c7889a2dcd37fd1dfd25b2c6" - integrity sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA== +"@babel/plugin-transform-runtime@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz#9d2a9dbf4e12644d6f46e5e75bfbf02b5d6e9194" + integrity sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw== dependencies: "@babel/helper-module-imports" "^7.18.6" "@babel/helper-plugin-utils" "^7.19.0" @@ -1035,12 +1040,12 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/preset-env@^7.12.11", "@babel/preset-env@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.3.tgz#52cd19abaecb3f176a4ff9cc5e15b7bf06bec754" - integrity sha512-ziye1OTc9dGFOAXSWKUqQblYHNlBOaDl8wzqf2iKXJAltYiR3hKHUKmkt+S9PppW7RQpq4fFCrwwpIDj/f5P4w== +"@babel/preset-env@^7.12.11", "@babel/preset-env@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.4.tgz#4c91ce2e1f994f717efb4237891c3ad2d808c94b" + integrity sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg== dependencies: - "@babel/compat-data" "^7.19.3" + "@babel/compat-data" "^7.19.4" "@babel/helper-compilation-targets" "^7.19.3" "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-validator-option" "^7.18.6" @@ -1055,7 +1060,7 @@ "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.18.9" + "@babel/plugin-proposal-object-rest-spread" "^7.19.4" "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" "@babel/plugin-proposal-optional-chaining" "^7.18.9" "@babel/plugin-proposal-private-methods" "^7.18.6" @@ -1079,10 +1084,10 @@ "@babel/plugin-transform-arrow-functions" "^7.18.6" "@babel/plugin-transform-async-to-generator" "^7.18.6" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.18.9" + "@babel/plugin-transform-block-scoping" "^7.19.4" "@babel/plugin-transform-classes" "^7.19.0" "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.18.13" + "@babel/plugin-transform-destructuring" "^7.19.4" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" @@ -1109,7 +1114,7 @@ "@babel/plugin-transform-unicode-escapes" "^7.18.10" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.19.3" + "@babel/types" "^7.19.4" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -1175,10 +1180,10 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.19.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" - integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.19.4", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" + integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== dependencies: regenerator-runtime "^0.13.4" @@ -1191,28 +1196,28 @@ "@babel/parser" "^7.18.10" "@babel/types" "^7.18.10" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.3", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.3", "@babel/traverse@^7.4.5": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.3.tgz#3a3c5348d4988ba60884e8494b0592b2f15a04b4" - integrity sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ== +"@babel/traverse@^7.10.3", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.19.4", "@babel/traverse@^7.19.6", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.6.tgz#7b4c865611df6d99cb131eec2e8ac71656a490dc" + integrity sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.3" + "@babel/generator" "^7.19.6" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.3" - "@babel/types" "^7.19.3" + "@babel/parser" "^7.19.6" + "@babel/types" "^7.19.4" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.10.3", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.3.tgz#fc420e6bbe54880bce6779ffaf315f5e43ec9624" - integrity sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw== +"@babel/types@^7.0.0", "@babel/types@^7.10.3", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" + integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== dependencies: - "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-string-parser" "^7.19.4" "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" @@ -2283,7 +2288,7 @@ dependencies: "@hapi/hoek" "^9.0.0" -"@hapi/validate@1.x.x", "@hapi/validate@^1.1.1": +"@hapi/validate@1.x.x", "@hapi/validate@^1.1.1", "@hapi/validate@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-1.1.3.tgz#f750a07283929e09b51aa16be34affb44e1931ad" integrity sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA== @@ -2348,61 +2353,61 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@jest/console@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" - integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== +"@jest/console@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" + integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^26.6.2" - jest-util "^26.6.2" + jest-message-util "^27.5.1" + jest-util "^27.5.1" slash "^3.0.0" -"@jest/core@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" - integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== - dependencies: - "@jest/console" "^26.6.2" - "@jest/reporters" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" +"@jest/core@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" + integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/reporters" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" + emittery "^0.8.1" exit "^0.1.2" - graceful-fs "^4.2.4" - jest-changed-files "^26.6.2" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-resolve-dependencies "^26.6.3" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - jest-watcher "^26.6.2" - micromatch "^4.0.2" - p-each-series "^2.1.0" + graceful-fs "^4.2.9" + jest-changed-files "^27.5.1" + jest-config "^27.5.1" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-resolve-dependencies "^27.5.1" + jest-runner "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + jest-watcher "^27.5.1" + micromatch "^4.0.4" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" - integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== +"@jest/environment@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" + integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== dependencies: - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" + jest-mock "^27.5.1" "@jest/expect-utils@^28.1.1": version "28.1.1" @@ -2411,58 +2416,57 @@ dependencies: jest-get-type "^28.0.2" -"@jest/fake-timers@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" - integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== +"@jest/fake-timers@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" + integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== dependencies: - "@jest/types" "^26.6.2" - "@sinonjs/fake-timers" "^6.0.1" + "@jest/types" "^27.5.1" + "@sinonjs/fake-timers" "^8.0.1" "@types/node" "*" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-util "^26.6.2" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-util "^27.5.1" -"@jest/globals@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" - integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== +"@jest/globals@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" + integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== dependencies: - "@jest/environment" "^26.6.2" - "@jest/types" "^26.6.2" - expect "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/types" "^27.5.1" + expect "^27.5.1" -"@jest/reporters@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" - integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== +"@jest/reporters@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" + integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.2" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.3" + istanbul-lib-instrument "^5.1.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^26.6.2" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" + istanbul-reports "^3.1.3" + jest-haste-map "^27.5.1" + jest-resolve "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" slash "^3.0.0" source-map "^0.6.0" string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^7.0.0" - optionalDependencies: - node-notifier "^8.0.0" + v8-to-istanbul "^8.1.0" "@jest/schemas@^28.0.2": version "28.0.2" @@ -2471,35 +2475,34 @@ dependencies: "@sinclair/typebox" "^0.23.3" -"@jest/source-map@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" - integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== +"@jest/source-map@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" + integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== dependencies: callsites "^3.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" source-map "^0.6.0" -"@jest/test-result@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" - integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== +"@jest/test-result@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" + integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== dependencies: - "@jest/console" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "^27.5.1" + "@jest/types" "^27.5.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" - integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== +"@jest/test-sequencer@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" + integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== dependencies: - "@jest/test-result" "^26.6.2" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" + "@jest/test-result" "^27.5.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-runtime "^27.5.1" "@jest/transform@^26.6.2": version "26.6.2" @@ -2522,7 +2525,28 @@ source-map "^0.6.1" write-file-atomic "^3.0.0" -"@jest/types@^26", "@jest/types@^26.6.2": +"@jest/transform@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" + integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^27.5.1" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-regex-util "^27.5.1" + jest-util "^27.5.1" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== @@ -2533,10 +2557,10 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@jest/types@^27.1.1": - version "27.1.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.1.1.tgz#77a3fc014f906c65752d12123a0134359707c0ad" - integrity sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA== +"@jest/types@^27", "@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" @@ -2605,10 +2629,6 @@ version "0.0.0" uid "" -"@kbn/adhoc-profiler@link:bazel-bin/packages/kbn-adhoc-profiler": - version "0.0.0" - uid "" - "@kbn/aiops-components@link:bazel-bin/x-pack/packages/ml/aiops_components": version "0.0.0" uid "" @@ -3097,6 +3117,18 @@ version "0.0.0" uid "" +"@kbn/core-lifecycle-server-internal@link:bazel-bin/packages/core/lifecycle/core-lifecycle-server-internal": + version "0.0.0" + uid "" + +"@kbn/core-lifecycle-server-mocks@link:bazel-bin/packages/core/lifecycle/core-lifecycle-server-mocks": + version "0.0.0" + uid "" + +"@kbn/core-lifecycle-server@link:bazel-bin/packages/core/lifecycle/core-lifecycle-server": + version "0.0.0" + uid "" + "@kbn/core-logging-server-internal@link:bazel-bin/packages/core/logging/core-logging-server-internal": version "0.0.0" uid "" @@ -3189,6 +3221,18 @@ version "0.0.0" uid "" +"@kbn/core-plugins-server-internal@link:bazel-bin/packages/core/plugins/core-plugins-server-internal": + version "0.0.0" + uid "" + +"@kbn/core-plugins-server-mocks@link:bazel-bin/packages/core/plugins/core-plugins-server-mocks": + version "0.0.0" + uid "" + +"@kbn/core-plugins-server@link:bazel-bin/packages/core/plugins/core-plugins-server": + version "0.0.0" + uid "" + "@kbn/core-preboot-server-internal@link:bazel-bin/packages/core/preboot/core-preboot-server-internal": version "0.0.0" uid "" @@ -4170,21 +4214,6 @@ resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz#c15367178d8bfe4765e6b47b542fe821ce259c7b" integrity sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ== -"@mapbox/node-pre-gyp@^1.0.0": - version "1.0.10" - resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" - integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== - dependencies: - detect-libc "^2.0.0" - https-proxy-agent "^5.0.0" - make-dir "^3.1.0" - node-fetch "^2.6.7" - nopt "^5.0.0" - npmlog "^5.0.1" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.11" - "@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2" @@ -4887,10 +4916,10 @@ dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" - integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== dependencies: "@sinonjs/commons" "^1.7.0" @@ -5872,10 +5901,10 @@ resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.1.3.tgz#fbb68696899d7b8c1b9b891eded9c04fe2cd5529" integrity sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A== -"@testing-library/dom@^8.0.0", "@testing-library/dom@^8.12.0": - version "8.12.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.12.0.tgz#fef5e545533fb084175dda6509ee71d7d2f72e23" - integrity sha512-rBrJk5WjI02X1edtiUcZhgyhgBhiut96r5Jp8J5qktKdcvLcZpKDW8i2hkGMMItxrghjXuQ5AM6aE0imnFawaw== +"@testing-library/dom@^8.0.0", "@testing-library/dom@^8.17.1": + version "8.17.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.17.1.tgz#2d7af4ff6dad8d837630fecd08835aee08320ad7" + integrity sha512-KnH2MnJUzmFNPW6RIKfd+zf2Wue8mEKX0M3cpX6aKl5ZXrJM1/c/Pc8c2xDNYQCnJO48Sm5ITbMXgqTr3h4jxQ== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" @@ -5886,30 +5915,27 @@ lz-string "^1.4.4" pretty-format "^27.0.2" -"@testing-library/jest-dom@^5.16.3": - version "5.16.3" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.3.tgz#b76851a909586113c20486f1679ffb4d8ec27bfa" - integrity sha512-u5DfKj4wfSt6akfndfu1eG06jsdyA/IUrlX2n3pyq5UXgXMhXY+NJb8eNK/7pqPWAhCKsCGWDdDO0zKMKAYkEA== +"@testing-library/jest-dom@^5.16.5": + version "5.16.5" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" + integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== dependencies: + "@adobe/css-tools" "^4.0.1" "@babel/runtime" "^7.9.2" "@types/testing-library__jest-dom" "^5.9.1" aria-query "^5.0.0" chalk "^3.0.0" - css "^3.0.0" css.escape "^1.5.1" dom-accessibility-api "^0.5.6" lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react-hooks@^7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz#3388d07f562d91e7f2431a4a21b5186062ecfee0" - integrity sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg== +"@testing-library/react-hooks@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" + integrity sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g== dependencies: "@babel/runtime" "^7.12.5" - "@types/react" ">=16.9.0" - "@types/react-dom" ">=16.9.0" - "@types/react-test-renderer" ">=16.9.0" react-error-boundary "^3.1.0" "@testing-library/react@^12.1.5": @@ -5933,6 +5959,11 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + "@trysound/sax@0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" @@ -6146,7 +6177,7 @@ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A== -"@types/babel__core@*", "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.19", "@types/babel__core@^7.1.7": +"@types/babel__core@*", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.19": version "7.1.19" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== @@ -6157,7 +6188,25 @@ "@types/babel__template" "*" "@types/babel__traverse" "*" -"@types/babel__generator@*", "@types/babel__generator@^7.6.4": +"@types/babel__core@^7.0.0": + version "7.1.10" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.10.tgz#ca58fc195dd9734e77e57c6f2df565623636ab40" + integrity sha512-x8OM8XzITIMyiwl5Vmo2B1cR1S1Ipkyv4mdlbJjMa1lmuKvKY9FrBbEANIaMlnWn5Rf7uO+rC/VgYabNkE17Hw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.0.2.tgz#d2112a6b21fad600d7674274293c85dce0cb47fc" + integrity sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__generator@^7.6.4": version "7.6.4" resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== @@ -6668,13 +6717,6 @@ resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== -"@types/http-proxy-agent@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/http-proxy-agent/-/http-proxy-agent-2.0.2.tgz#942c1f35c7e1f0edd1b6ffae5d0f9051cfb32be1" - integrity sha512-2S6IuBRhqUnH1/AUx9k8KWtY3Esg4eqri946MnxTG5HwehF1S5mqLln8fcyMiuQkY72p2gH3W+rIPqp5li0LyQ== - dependencies: - "@types/node" "*" - "@types/http-proxy@^1.17.4", "@types/http-proxy@^1.17.8": version "1.17.9" resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" @@ -6734,7 +6776,7 @@ dependencies: "@types/jest" "*" -"@types/jest@*", "@types/jest@^26.0.16", "@types/jest@^26.0.22": +"@types/jest@*", "@types/jest@^26.0.16": version "26.0.22" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.22.tgz#8308a1debdf1b807aa47be2838acdcd91e88fbe6" integrity sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw== @@ -6742,6 +6784,14 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" +"@types/jest@^27.4.1": + version "27.5.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c" + integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA== + dependencies: + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" + "@types/joi@^17.2.3": version "17.2.3" resolved "https://registry.yarnpkg.com/@types/joi/-/joi-17.2.3.tgz#b7768ed9d84f1ebd393328b9f97c1cf3d2b94798" @@ -6816,10 +6866,6 @@ version "0.0.0" uid "" -"@types/kbn__adhoc-profiler@link:bazel-bin/packages/kbn-adhoc-profiler/npm_module_types": - version "0.0.0" - uid "" - "@types/kbn__aiops-components@link:bazel-bin/x-pack/packages/ml/aiops_components/npm_module_types": version "0.0.0" uid "" @@ -7304,6 +7350,18 @@ version "0.0.0" uid "" +"@types/kbn__core-lifecycle-server-internal@link:bazel-bin/packages/core/lifecycle/core-lifecycle-server-internal/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-lifecycle-server-mocks@link:bazel-bin/packages/core/lifecycle/core-lifecycle-server-mocks/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-lifecycle-server@link:bazel-bin/packages/core/lifecycle/core-lifecycle-server/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__core-logging-server-internal@link:bazel-bin/packages/core/logging/core-logging-server-internal/npm_module_types": version "0.0.0" uid "" @@ -7396,6 +7454,18 @@ version "0.0.0" uid "" +"@types/kbn__core-plugins-server-internal@link:bazel-bin/packages/core/plugins/core-plugins-server-internal/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-plugins-server-mocks@link:bazel-bin/packages/core/plugins/core-plugins-server-mocks/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-plugins-server@link:bazel-bin/packages/core/plugins/core-plugins-server/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__core-preboot-server-internal@link:bazel-bin/packages/core/preboot/core-preboot-server-internal/npm_module_types": version "0.0.0" uid "" @@ -8474,7 +8544,7 @@ dependencies: "@types/node" "*" -"@types/prettier@^2.0.0", "@types/prettier@^2.3.2": +"@types/prettier@^2.0.0", "@types/prettier@^2.1.5", "@types/prettier@^2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3" integrity sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog== @@ -8538,7 +8608,7 @@ dependencies: "@types/react" "*" -"@types/react-dom@<18.0.0", "@types/react-dom@>=16.9.0", "@types/react-dom@^17.0.17": +"@types/react-dom@<18.0.0", "@types/react-dom@^17.0.17": version "17.0.17" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.17.tgz#2e3743277a793a96a99f1bf87614598289da68a1" integrity sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg== @@ -8629,7 +8699,7 @@ dependencies: "@types/react" "*" -"@types/react-test-renderer@>=16.9.0", "@types/react-test-renderer@^17.0.2": +"@types/react-test-renderer@^17.0.2": version "17.0.2" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.2.tgz#5f800a39b12ac8d2a2149e7e1885215bcf4edbbf" integrity sha512-+F1KONQTBHDBBhbHuT2GNydeMpPuviduXIVJRB7Y4nma4NR5DrTJfMMZ+jbhEHbpwL+Uqhs1WXh4KHiyrtYTPg== @@ -8672,7 +8742,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@>=16.9.0", "@types/react@^17", "@types/react@^17.0.45": +"@types/react@*", "@types/react@^17", "@types/react@^17.0.45": version "17.0.45" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.45.tgz#9b3d5b661fd26365fefef0e766a1c6c30ccf7b3f" integrity sha512-YfhQ22Lah2e3CHPsb93tRwIGNiSwkuz1/blk4e6QrWS0jQzCSNbGLtOEYhPg02W0yGTTmpajp7dCTbBAMN3qsg== @@ -9582,7 +9652,7 @@ JSONStream@1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" -abab@^2.0.3, abab@^2.0.4: +abab@^2.0.3, abab@^2.0.4, abab@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== @@ -9659,7 +9729,7 @@ acorn@^7.0.0, acorn@^7.1.1, acorn@^7.4.0, acorn@^7.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1: +acorn@^8.0.4, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1: version "8.8.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== @@ -9679,13 +9749,6 @@ after-all-results@^2.0.0: resolved "https://registry.yarnpkg.com/after-all-results/-/after-all-results-2.0.0.tgz#6ac2fc202b500f88da8f4f5530cfa100f4c6a2d0" integrity sha1-asL8ICtQD4jaj09VMM+hAPTGotA= -agent-base@4: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -10361,7 +10424,7 @@ atob-lite@^2.0.0: resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY= -atob@^2.1.1, atob@^2.1.2: +atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== @@ -10474,18 +10537,18 @@ axobject-query@^2.2.0: resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== -babel-jest@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" - integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== - dependencies: - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/babel__core" "^7.1.7" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.6.2" +babel-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" + integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== + dependencies: + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^27.5.1" chalk "^4.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" slash "^3.0.0" babel-loader@^8.0.0, babel-loader@^8.2.5: @@ -10557,10 +10620,10 @@ babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" - integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== +babel-plugin-jest-hoist@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" + integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -10679,12 +10742,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" - integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== +babel-preset-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" + integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== dependencies: - babel-plugin-jest-hoist "^26.6.2" + babel-plugin-jest-hoist "^27.5.1" babel-preset-current-node-syntax "^1.0.0" babel-runtime@6.x, babel-runtime@^6.26.0: @@ -10834,13 +10897,6 @@ binary-search@^1.3.3: resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c" integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA== -bindings@^1.2.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - bitmap-sdf@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/bitmap-sdf/-/bitmap-sdf-1.0.3.tgz#c99913e5729357a6fd350de34158180c013880b2" @@ -10997,6 +11053,14 @@ brfs@^2.0.0, brfs@^2.0.2: static-module "^3.0.2" through2 "^2.0.0" +brok@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/brok/-/brok-5.0.2.tgz#b77e7203ce89d30939a5b877a9bb3acb4dffc848" + integrity sha512-mqsoOGPjcP9oltC8dD4PnRCiJREmFg+ee588mVYZgZNd8YV5Zo6eOLv/fp6HxdYffaxvkKfPHjc+sRWIkuIu7A== + dependencies: + "@hapi/hoek" "^9.0.4" + "@hapi/validate" "^1.1.3" + brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -11644,10 +11708,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -cjs-module-lexer@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" - integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== clamp@^1.0.1: version "1.0.1" @@ -12595,15 +12659,6 @@ css@2.X, css@^2.2.1, css@^2.2.4: source-map-resolve "^0.5.2" urix "^0.1.0" -css@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" - integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== - dependencies: - inherits "^2.0.4" - source-map "^0.6.1" - source-map-resolve "^0.6.0" - csscolorparser@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b" @@ -12685,7 +12740,7 @@ cssom@~0.3.6: resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== -cssstyle@^2.2.0: +cssstyle@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== @@ -13310,7 +13365,7 @@ decamelize@^4.0.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -decimal.js@^10.2.0: +decimal.js@^10.2.1: version "10.3.1" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== @@ -13509,11 +13564,6 @@ delaunator@5: dependencies: robust-predicates "^3.0.0" -delay@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" - integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -14019,45 +14069,7 @@ elastic-apm-http-client@11.0.1, elastic-apm-http-client@^11.0.1: semver "^6.3.0" stream-chopper "^3.0.1" -elastic-apm-node@^3.38.0: - version "3.38.0" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.38.0.tgz#4d0dc9279c0e23e09b3b30aa4a9f9aeccfa59cc0" - integrity sha512-/d6YuWFtsfkVRpFD0YJ2rYJVq0rI0PGqG/C+cW1JpMZ4IOU8dA9xzUkxbT0G3B8gpHNug07Xo6bJdQa2oUaFbQ== - dependencies: - "@elastic/ecs-pino-format" "^1.2.0" - "@opentelemetry/api" "^1.1.0" - after-all-results "^2.0.0" - async-cache "^1.1.0" - async-value-promise "^1.1.1" - basic-auth "^2.0.1" - cookie "^0.5.0" - core-util-is "^1.0.2" - elastic-apm-http-client "11.0.1" - end-of-stream "^1.4.4" - error-callsites "^2.0.4" - error-stack-parser "^2.0.6" - escape-string-regexp "^4.0.0" - fast-safe-stringify "^2.0.7" - http-headers "^3.0.2" - is-native "^1.0.1" - lru-cache "^6.0.0" - measured-reporting "^1.51.1" - monitor-event-loop-delay "^1.0.0" - object-filter-sequence "^1.0.0" - object-identity-map "^1.0.2" - original-url "^1.2.3" - pino "^6.11.2" - relative-microtime "^2.0.0" - require-in-the-middle "^5.0.3" - semver "^6.3.0" - set-cookie-serde "^1.0.0" - shallow-clone-shim "^2.0.0" - source-map "^0.8.0-beta.0" - sql-summary "^1.0.1" - traverse "^0.6.6" - unicode-byte-truncate "^1.0.0" - -elastic-apm-node@^3.39.0: +elastic-apm-node@^3.38.0, elastic-apm-node@^3.39.0: version "3.39.0" resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.39.0.tgz#51ca1dfc11e6b48b53967518461a959ac1623da1" integrity sha512-aNRLDMQreZ+u23HStmppdDNtfS7Z651MWf3wLjw72haCNpGczuXsb4EuBRfJOk0IXWXTYgX1cDy2hiy4PAxlSQ== @@ -14139,10 +14151,10 @@ email-addresses@^5.0.0: resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-5.0.0.tgz#7ae9e7f58eef7d5e3e2c2c2d3ea49b78dc854fa6" integrity sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw== -emittery@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.1.tgz#c02375a927a40948c0345cc903072597f5270451" - integrity sha512-d34LN4L6h18Bzz9xpoku2nPwKxCPlPMr3EEKTkoEBi+1/+b0lcRkRJ1UVyyZaKNeqGR3swcGl6s390DNO4YVgQ== +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== "emoji-regex@>=6.0.0 <=6.1.1": version "6.1.1" @@ -14269,13 +14281,13 @@ enzyme-shallow-equal@^1.0.0, enzyme-shallow-equal@^1.0.1: has "^1.0.3" object-is "^1.1.2" -enzyme-to-json@^3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.6.1.tgz#d60740950bc7ca6384dfe6fe405494ec5df996bc" - integrity sha512-15tXuONeq5ORoZjV/bUo2gbtZrN2IH+Z6DvL35QmZyKHgbY1ahn6wcnLd9Xv9OjiwbAXiiP8MRZwbZrCv1wYNg== +enzyme-to-json@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.6.2.tgz#94f85c413bcae8ab67be53b0a94b69a560e27823" + integrity sha512-Ynm6Z6R6iwQ0g2g1YToz6DWhxVnt8Dy1ijR2zynRKxTyBGA8rCDXU3rs2Qc4OKvUvc2Qoe1bcFK6bnPs20TrTg== dependencies: "@types/cheerio" "^0.22.22" - lodash "^4.17.15" + lodash "^4.17.21" react-is "^16.12.0" enzyme@^3.11.0: @@ -14442,18 +14454,11 @@ es6-map@^0.1.5: es6-symbol "~3.1.1" event-emitter "~0.3.5" -es6-promise@^4.0.3, es6-promise@^4.2.8: +es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - es6-set@^0.1.5, es6-set@~0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" @@ -14518,7 +14523,7 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escodegen@^1.11.1, escodegen@^1.14.1: +escodegen@^1.11.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== @@ -14955,7 +14960,7 @@ exec-sh@^0.3.2: resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b" integrity sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg== -execa@4.1.0, execa@^4.0.0, execa@^4.0.2: +execa@4.1.0, execa@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== @@ -15057,17 +15062,15 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" -expect@^27.0.2: - version "27.2.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-27.2.0.tgz#40eb89a492afb726a3929ccf3611ee0799ab976f" - integrity sha512-oOTbawMQv7AK1FZURbPTgGSzmhxkjFzoARSvDjOMnOpeWuYQx1tP6rXu9MIX5mrACmyCAM7fSNP8IJO2f1p0CQ== +expect@^27.0.2, expect@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== dependencies: - "@jest/types" "^27.1.1" - ansi-styles "^5.0.0" - jest-get-type "^27.0.6" - jest-matcher-utils "^27.2.0" - jest-message-util "^27.2.0" - jest-regex-util "^27.0.6" + "@jest/types" "^27.5.1" + jest-get-type "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" expect@^28.1.1: version "28.1.1" @@ -15445,11 +15448,6 @@ file-system-cache@^1.0.5: fs-extra "^0.30.0" ramda "^0.21.0" -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - filelist@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" @@ -15548,11 +15546,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -findit2@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/findit2/-/findit2-2.2.3.tgz#58a466697df8a6205cdfdbf395536b8bd777a5f6" - integrity sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog== - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -15687,9 +15680,18 @@ form-data@^2.3.1, form-data@^2.3.3: combined-stream "^1.0.6" mime-types "^2.1.12" -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" @@ -15866,7 +15868,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.1.2, fsevents@~2.3.2: +fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -16433,11 +16435,6 @@ graphql@^16.6.0: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= - gulp-brotli@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/gulp-brotli/-/gulp-brotli-3.0.0.tgz#7f5a1d8a6d43cab28056f9e56f29ae071dcfe4b4" @@ -17037,14 +17034,6 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" @@ -17054,6 +17043,15 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + http-proxy-middleware@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" @@ -17882,7 +17880,7 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-potential-custom-element-name@^1.0.0: +is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== @@ -18146,7 +18144,7 @@ istanbul-lib-hook@^3.0.0: dependencies: append-transform "^2.0.0" -istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: +istanbul-lib-instrument@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== @@ -18156,7 +18154,7 @@ istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: istanbul-lib-coverage "^3.0.0" semver "^6.3.0" -istanbul-lib-instrument@^5.0.4: +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== @@ -18198,10 +18196,10 @@ istanbul-lib-source-maps@^4.0.0: istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" - integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== +istanbul-reports@^3.0.2, istanbul-reports@^3.1.3: + version "3.1.4" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" + integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" @@ -18244,92 +18242,95 @@ jest-axe@^5.0.0: jest-matcher-utils "27.0.2" lodash.merge "4.6.2" -jest-canvas-mock@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.3.1.tgz#9535d14bc18ccf1493be36ac37dd349928387826" - integrity sha512-5FnSZPrX3Q2ZfsbYNE3wqKR3+XorN8qFzDzB5o0golWgt6EOX1+emBnpOc9IAQ+NXFj8Nzm3h7ZdE/9H0ylBcg== +jest-canvas-mock@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.4.0.tgz#947b71442d7719f8e055decaecdb334809465341" + integrity sha512-mmMpZzpmLzn5vepIaHk5HoH3Ka4WykbSoLuG/EKoJd0x0ID/t+INo1l8ByfcUJuDM+RIsL4QDg/gDnBbrj2/IQ== dependencies: cssfontparser "^1.2.1" moo-color "^1.0.2" -jest-changed-files@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" - integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== +jest-changed-files@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" + integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== dependencies: - "@jest/types" "^26.6.2" - execa "^4.0.0" - throat "^5.0.0" + "@jest/types" "^27.5.1" + execa "^5.0.0" + throat "^6.0.1" -jest-circus@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-26.6.3.tgz#3cc7ef2a6a3787e5d7bfbe2c72d83262154053e7" - integrity sha512-ACrpWZGcQMpbv13XbzRzpytEJlilP/Su0JtNCi5r/xLpOUhnaIJr8leYYpLEMgPFURZISEHrnnpmB54Q/UziPw== +jest-circus@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" + integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/babel__traverse" "^7.0.4" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" - expect "^26.6.2" + expect "^27.5.1" is-generator-fn "^2.0.0" - jest-each "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - pretty-format "^26.6.2" - stack-utils "^2.0.2" - throat "^5.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" -jest-cli@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" - integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== +jest-cli@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" + integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== dependencies: - "@jest/core" "^26.6.3" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/core" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" chalk "^4.0.0" exit "^0.1.2" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" import-local "^3.0.2" - is-ci "^2.0.0" - jest-config "^26.6.3" - jest-util "^26.6.2" - jest-validate "^26.6.2" + jest-config "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" prompts "^2.0.1" - yargs "^15.4.1" + yargs "^16.2.0" -jest-config@^26, jest-config@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" - integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== +jest-config@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" + integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.6.3" - "@jest/types" "^26.6.2" - babel-jest "^26.6.3" + "@babel/core" "^7.8.0" + "@jest/test-sequencer" "^27.5.1" + "@jest/types" "^27.5.1" + babel-jest "^27.5.1" chalk "^4.0.0" + ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.1" - graceful-fs "^4.2.4" - jest-environment-jsdom "^26.6.2" - jest-environment-node "^26.6.2" - jest-get-type "^26.3.0" - jest-jasmine2 "^26.6.3" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - micromatch "^4.0.2" - pretty-format "^26.6.2" + graceful-fs "^4.2.9" + jest-circus "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-get-type "^27.5.1" + jest-jasmine2 "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runner "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^27.5.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" jest-diff@^26.0.0, jest-diff@^26.6.2: version "26.6.2" @@ -18341,7 +18342,7 @@ jest-diff@^26.0.0, jest-diff@^26.6.2: jest-get-type "^26.3.0" pretty-format "^26.6.2" -jest-diff@^27.0.2, jest-diff@^27.2.0: +jest-diff@^27.0.2, jest-diff@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== @@ -18361,55 +18362,55 @@ jest-diff@^28.1.1: jest-get-type "^28.0.2" pretty-format "^28.1.1" -jest-docblock@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" - integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== +jest-docblock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" + integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== dependencies: detect-newline "^3.0.0" -jest-each@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" - integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== +jest-each@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" + integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" chalk "^4.0.0" - jest-get-type "^26.3.0" - jest-util "^26.6.2" - pretty-format "^26.6.2" + jest-get-type "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" -jest-environment-jsdom@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" - integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== +jest-environment-jsdom@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" + integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" - jsdom "^16.4.0" + jest-mock "^27.5.1" + jest-util "^27.5.1" + jsdom "^16.6.0" -jest-environment-node@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" - integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== +jest-environment-node@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" + integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" + jest-mock "^27.5.1" + jest-util "^27.5.1" jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-get-type@^27.0.1, jest-get-type@^27.0.6, jest-get-type@^27.5.1: +jest-get-type@^27.0.1, jest-get-type@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== @@ -18440,37 +18441,56 @@ jest-haste-map@^26.6.2: optionalDependencies: fsevents "^2.1.2" -jest-jasmine2@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" - integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== +jest-haste-map@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" + integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^27.5.1" + jest-serializer "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + micromatch "^4.0.4" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + +jest-jasmine2@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" + integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - expect "^26.6.2" + expect "^27.5.1" is-generator-fn "^2.0.0" - jest-each "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - pretty-format "^26.6.2" - throat "^5.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + throat "^6.0.1" -jest-leak-detector@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" - integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== +jest-leak-detector@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" + integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== dependencies: - jest-get-type "^26.3.0" - pretty-format "^26.6.2" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" jest-matcher-utils@27.0.2: version "27.0.2" @@ -18492,15 +18512,15 @@ jest-matcher-utils@^26.6.2: jest-get-type "^26.3.0" pretty-format "^26.6.2" -jest-matcher-utils@^27.2.0: - version "27.2.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.2.0.tgz#b4d224ab88655d5fab64b96b989ac349e2f5da43" - integrity sha512-F+LG3iTwJ0gPjxBX6HCyrARFXq6jjiqhwBQeskkJQgSLeF1j6ui1RTV08SR7O51XTUhtc8zqpDj8iCG4RGmdKw== +jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== dependencies: chalk "^4.0.0" - jest-diff "^27.2.0" - jest-get-type "^27.0.6" - pretty-format "^27.2.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" jest-matcher-utils@^28.1.1: version "28.1.1" @@ -18527,18 +18547,18 @@ jest-message-util@^26.6.2: slash "^3.0.0" stack-utils "^2.0.2" -jest-message-util@^27.2.0: - version "27.2.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.2.0.tgz#2f65c71df55267208686b1d7514e18106c91ceaf" - integrity sha512-y+sfT/94CiP8rKXgwCOzO1mUazIEdEhrLjuiu+RKmCP+8O/TJTSne9dqQRbFIHBtlR2+q7cddJlWGir8UATu5w== +jest-message-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^27.1.1" + "@jest/types" "^27.5.1" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^27.2.0" + pretty-format "^27.5.1" slash "^3.0.0" stack-utils "^2.0.3" @@ -18557,12 +18577,12 @@ jest-message-util@^28.1.1: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" - integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== +jest-mock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" jest-pnp-resolver@^1.2.2: @@ -18580,19 +18600,19 @@ jest-regex-util@^26.0.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== -jest-regex-util@^27.0.6: - version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.0.6.tgz#02e112082935ae949ce5d13b2675db3d8c87d9c5" - integrity sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ== +jest-regex-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" + integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== -jest-resolve-dependencies@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" - integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== +jest-resolve-dependencies@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" + integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== dependencies: - "@jest/types" "^26.6.2" - jest-regex-util "^26.0.0" - jest-snapshot "^26.6.2" + "@jest/types" "^27.5.1" + jest-regex-util "^27.5.1" + jest-snapshot "^27.5.1" jest-resolve@^26.6.2: version "26.6.2" @@ -18608,64 +18628,76 @@ jest-resolve@^26.6.2: resolve "^1.18.1" slash "^3.0.0" -jest-runner@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" - integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== +jest-resolve@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" + integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-pnp-resolver "^1.2.2" + jest-util "^27.5.1" + jest-validate "^27.5.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" + integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - emittery "^0.7.1" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-docblock "^26.0.0" - jest-haste-map "^26.6.2" - jest-leak-detector "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" - jest-runtime "^26.6.3" - jest-util "^26.6.2" - jest-worker "^26.6.2" + emittery "^0.8.1" + graceful-fs "^4.2.9" + jest-docblock "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-haste-map "^27.5.1" + jest-leak-detector "^27.5.1" + jest-message-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runtime "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" source-map-support "^0.5.6" - throat "^5.0.0" - -jest-runtime@^26, jest-runtime@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" - integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== - dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/globals" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/yargs" "^15.0.0" + throat "^6.0.1" + +jest-runtime@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" + integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/globals" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" chalk "^4.0.0" - cjs-module-lexer "^0.6.0" + cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" - exit "^0.1.2" + execa "^5.0.0" glob "^7.1.3" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" slash "^3.0.0" strip-bom "^4.0.0" - yargs "^15.4.1" jest-serializer@^26.6.2: version "26.6.2" @@ -18675,6 +18707,14 @@ jest-serializer@^26.6.2: "@types/node" "*" graceful-fs "^4.2.4" +jest-serializer@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" + integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.9" + jest-silent-reporter@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jest-silent-reporter/-/jest-silent-reporter-0.5.0.tgz#5fd8ccd61665227e3bf19d908b7350719d06ff38" @@ -18683,7 +18723,7 @@ jest-silent-reporter@^0.5.0: chalk "^4.0.0" jest-util "^26.0.0" -jest-snapshot@^26.3.0, jest-snapshot@^26.6.2: +jest-snapshot@^26.3.0: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== @@ -18705,6 +18745,34 @@ jest-snapshot@^26.3.0, jest-snapshot@^26.6.2: pretty-format "^26.6.2" semver "^7.3.2" +jest-snapshot@^27.0.2, jest-snapshot@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" + integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.0.0" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^27.5.1" + graceful-fs "^4.2.9" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + jest-haste-map "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + natural-compare "^1.4.0" + pretty-format "^27.5.1" + semver "^7.3.2" + jest-specific-snapshot@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jest-specific-snapshot/-/jest-specific-snapshot-4.0.0.tgz#a52a2e223e7576e610dbeaf341207c557ac20554" @@ -18712,7 +18780,14 @@ jest-specific-snapshot@^4.0.0: dependencies: jest-snapshot "^26.3.0" -jest-styled-components@^7.0.3: +jest-specific-snapshot@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/jest-specific-snapshot/-/jest-specific-snapshot-5.0.0.tgz#48f72d5613af7f3e30df75b6b3534db6bab32ea0" + integrity sha512-V65vuPxZQExD3tGbv+Du5tbG1E3H3Dq/HFbsCEkPJP27w5vr/nATQJl61Dx5doBfu54OrJak0JaeYVSeZubDKg== + dependencies: + jest-snapshot "^27.0.2" + +jest-styled-components@7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-7.0.3.tgz#cc0b031f910484e68f175568682f3969ff774b2c" integrity sha512-jj9sWyshehUnB0P9WFUaq9Bkh6RKYO8aD8lf3gUrXRwg/MRddTFk7U9D9pC4IAI3v9fbz4vmrMxwaecTpG8NKA== @@ -18731,6 +18806,18 @@ jest-util@^26.0.0, jest-util@^26.6.2: is-ci "^2.0.0" micromatch "^4.0.2" +jest-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-util@^28.1.1: version "28.1.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.1.tgz#ff39e436a1aca397c0ab998db5a51ae2b7080d05" @@ -18743,29 +18830,29 @@ jest-util@^28.1.1: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" - integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== +jest-validate@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" + integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== dependencies: - "@jest/types" "^26.6.2" - camelcase "^6.0.0" + "@jest/types" "^27.5.1" + camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^26.3.0" + jest-get-type "^27.5.1" leven "^3.1.0" - pretty-format "^26.6.2" + pretty-format "^27.5.1" -jest-watcher@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" - integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== +jest-watcher@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" + integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== dependencies: - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^26.6.2" + jest-util "^27.5.1" string-length "^4.0.1" jest-worker@^26.5.0, jest-worker@^26.6.2: @@ -18777,7 +18864,7 @@ jest-worker@^26.5.0, jest-worker@^26.6.2: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^27.4.5: +jest-worker@^27.4.5, jest-worker@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== @@ -18786,14 +18873,14 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" - integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== +jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" + integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== dependencies: - "@jest/core" "^26.6.3" + "@jest/core" "^27.5.1" import-local "^3.0.2" - jest-cli "^26.6.3" + jest-cli "^27.5.1" joi@*, joi@^17.3.0, joi@^17.4.0: version "17.4.0" @@ -18892,36 +18979,37 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^16.4.0: - version "16.4.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb" - integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w== +jsdom@^16.4.0, jsdom@^16.6.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== dependencies: - abab "^2.0.3" - acorn "^7.1.1" + abab "^2.0.5" + acorn "^8.2.4" acorn-globals "^6.0.0" cssom "^0.4.4" - cssstyle "^2.2.0" + cssstyle "^2.3.0" data-urls "^2.0.0" - decimal.js "^10.2.0" + decimal.js "^10.2.1" domexception "^2.0.1" - escodegen "^1.14.1" + escodegen "^2.0.0" + form-data "^3.0.0" html-encoding-sniffer "^2.0.1" - is-potential-custom-element-name "^1.0.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" nwsapi "^2.2.0" - parse5 "5.1.1" - request "^2.88.2" - request-promise-native "^1.0.8" - saxes "^5.0.0" + parse5 "6.0.1" + saxes "^5.0.1" symbol-tree "^3.2.4" - tough-cookie "^3.0.1" + tough-cookie "^4.0.0" w3c-hr-time "^1.0.2" w3c-xmlserializer "^2.0.0" webidl-conversions "^6.1.0" whatwg-encoding "^1.0.5" whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" - ws "^7.2.3" + whatwg-url "^8.5.0" + ws "^7.4.6" xml-name-validator "^3.0.0" jsesc@^2.5.1: @@ -20877,11 +20965,6 @@ nan@^2.13.2, nan@^2.14.2, nan@^2.15.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== -nan@^2.14.0: - version "2.17.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" - integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== - nano-css@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.2.1.tgz#73b8470fa40b028a134d3393ae36bbb34b9fa332" @@ -21172,18 +21255,6 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-notifier@^8.0.0: - version "8.0.1" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1" - integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA== - dependencies: - growly "^1.3.0" - is-wsl "^2.2.0" - semver "^7.3.2" - shellwords "^0.1.1" - uuid "^8.3.0" - which "^2.0.2" - node-preload@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" @@ -21817,11 +21888,6 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== -p-each-series@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" - integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ== - p-event@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.1.0.tgz#e92bb866d7e8e5b732293b1c8269d38e9982bf8e" @@ -21855,7 +21921,7 @@ p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.3.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.0, p-limit@^3.0.1, p-limit@^3.0.2: +p-limit@^3.0.1, p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -22051,7 +22117,7 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -parse-json@^5.0.0: +parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -22078,12 +22144,7 @@ parse5-htmlparser2-tree-adapter@^6.0.1: dependencies: parse5 "^6.0.1" -parse5@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== - -parse5@^6.0.0, parse5@^6.0.1: +parse5@6.0.1, parse5@^6.0.0, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -22287,11 +22348,6 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" - integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== - pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -22321,7 +22377,7 @@ pino@^6.11.2: quick-format-unescaped "^4.0.3" sonic-boom "^1.0.2" -pirates@^4.0.1, pirates@^4.0.5: +pirates@^4.0.1, pirates@^4.0.4, pirates@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== @@ -22834,22 +22890,6 @@ potpack@^1.0.2: resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14" integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ== -pprof@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/pprof/-/pprof-3.2.0.tgz#5a60638dc51a61128a3d57c74514e8fd99e93722" - integrity sha512-yhORhVWefg94HZgjVa6CDtYSNZJnJzZ82d4pkmrZJxf1/Y29Me/uHYLEVo6KawKKFhQywl5cGbkdnVx9bZoMew== - dependencies: - "@mapbox/node-pre-gyp" "^1.0.0" - bindings "^1.2.1" - delay "^5.0.0" - findit2 "^2.2.3" - nan "^2.14.0" - p-limit "^3.0.0" - pify "^5.0.0" - protobufjs "~6.11.0" - source-map "^0.7.3" - split "^1.0.1" - preact-render-to-string@^5.1.19: version "5.1.19" resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-5.1.19.tgz#ffae7c3bd1680be5ecf5991d41fe3023b3051e0e" @@ -22935,7 +22975,7 @@ pretty-format@^26.0.0, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" -pretty-format@^27.0.2, pretty-format@^27.2.0, pretty-format@^27.5.1: +pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== @@ -23130,7 +23170,7 @@ protobufjs@6.8.8: "@types/node" "^10.1.0" long "^4.0.0" -protobufjs@^6.11.3, protobufjs@~6.11.0: +protobufjs@^6.11.3: version "6.11.3" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== @@ -23653,10 +23693,10 @@ react-grid-layout@^1.3.4: react-draggable "^4.0.0" react-resizable "^3.0.4" -react-hook-form@^7.37.0: - version "7.37.0" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.37.0.tgz#4d1738f092d3d8a3ade34ee892d97350b1032b19" - integrity sha512-6NFTxsnw+EXSpNNvLr5nFMjPdYKRryQcelTHg7zwBB6vAzfPIcZq4AExP4heVlwdzntepQgwiOQW4z7Mr99Lsg== +react-hook-form@^7.38.0: + version "7.38.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.38.0.tgz#53d6a68df587ce4ce88352f63e0ecc7fc8779320" + integrity sha512-gxWW1kMeru9xR1GoR+Iw4hA+JBOM3SHfr4DWCUKY0xc7Vv1MLsF109oHtBeWl9shcyPFx67KHru44DheN0XY5A== react-input-autosize@^3.0.0: version "3.0.0" @@ -24683,23 +24723,7 @@ request-progress@^3.0.0: dependencies: throttleit "^1.0.0" -request-promise-core@1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" - integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== - dependencies: - lodash "^4.17.19" - -request-promise-native@^1.0.8: - version "1.0.9" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" - integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== - dependencies: - request-promise-core "1.1.4" - stealthy-require "^1.1.1" - tough-cookie "^2.3.3" - -request@^2.44.0, request@^2.88.0, request@^2.88.2: +request@^2.44.0, request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -24735,7 +24759,7 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -require-in-the-middle@^5.0.3, require-in-the-middle@^5.2.0: +require-in-the-middle@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz#4b71e3cc7f59977100af9beb76bf2d056a5a6de2" integrity sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg== @@ -24815,6 +24839,11 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" @@ -25144,7 +25173,7 @@ sax@>=0.6.0, sax@^1.2.1: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -saxes@^5.0.0: +saxes@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== @@ -25501,11 +25530,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - side-channel@^1.0.2, side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -25766,14 +25790,6 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: source-map-url "^0.4.0" urix "^0.1.0" -source-map-resolve@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" - integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - source-map-support@0.5.9: version "0.5.9" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" @@ -25957,13 +25973,6 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" -split@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -26124,11 +26133,6 @@ stdout-stream@^1.4.0: dependencies: readable-stream "^2.0.1" -stealthy-require@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" - integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= - store2@^2.12.0: version "2.12.0" resolved "https://registry.yarnpkg.com/store2/-/store2-2.12.0.tgz#e1f1b7e1a59b6083b2596a8d067f6ee88fd4d3cf" @@ -26891,10 +26895,10 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -throat@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" - integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== throttle-debounce@^2.1.0: version "2.1.0" @@ -26957,7 +26961,7 @@ through2@~0.4.1: readable-stream "~1.0.17" xtend "~2.1.1" -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3.4: +"through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3.4: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -27168,24 +27172,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^2.3.3, tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - -tough-cookie@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" - integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== - dependencies: - ip-regex "^2.1.0" - psl "^1.1.28" - punycode "^2.1.1" - -tough-cookie@^4.1.2: +tough-cookie@^4.0.0, tough-cookie@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== @@ -27195,6 +27182,14 @@ tough-cookie@^4.1.2: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -28071,10 +28066,10 @@ v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -v8-to-istanbul@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz#b4fe00e35649ef7785a9b7fcebcea05f37c332fc" - integrity sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA== +v8-to-istanbul@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz#0aeb763894f1a0a1676adf8a8b7612a38902446c" + integrity sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" @@ -28955,7 +28950,7 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" -whatwg-url@^8.0.0: +whatwg-url@^8.0.0, whatwg-url@^8.5.0: version "8.7.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== @@ -29182,17 +29177,12 @@ write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@8.9.0: +ws@8.9.0, ws@>=8.7.0, ws@^8.2.3, ws@^8.4.2: version "8.9.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.9.0.tgz#2a994bb67144be1b53fe2d23c53c028adeb7f45e" integrity sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg== -ws@>=8.7.0, ws@^8.2.3, ws@^8.4.2: - version "8.8.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769" - integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ== - -ws@^7.2.3, ws@^7.3.1, ws@^7.4.6: +ws@^7.3.1, ws@^7.4.6: version "7.5.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== @@ -29287,10 +29277,10 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm@^4.18.0: - version "4.18.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0.tgz#a1f6ab2c330c3918fb094ae5f4c2562987398ea1" - integrity sha512-JQoc1S0dti6SQfI0bK1AZvGnAxH4MVw45ZPFSO6FHTInAiau3Ix77fSxNx3mX4eh9OL4AYa8+4C8f5UvnSfppQ== +xterm@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0.tgz#0af50509b33d0dc62fde7a4ec17750b8e453cc5c" + integrity sha512-tmVsKzZovAYNDIaUinfz+VDclraQpPUnAME+JawosgWRMphInDded/PuY0xmU5dOhyeYZsI0nz5yd8dPYsdLTA== y18n@^3.2.0: version "3.2.2"